From 55b415ae2c5ded74c78b5b9c93045c92a820a2c7 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 9 Apr 2025 05:02:16 +0000 Subject: [PATCH 001/114] Update dependencies from https://github.com/dotnet/arcade build 20250408.6 Microsoft.SourceBuild.Intermediate.arcade , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.XliffTasks From Version 9.0.0-beta.25204.5 -> To Version 9.0.0-beta.25208.6 --- eng/Version.Details.xml | 16 ++++++++-------- global.json | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7895406998886..0edf3248a9e47 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -127,19 +127,19 @@ - + https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 - + https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 - + https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 https://github.com/dotnet/symreader @@ -155,9 +155,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 https://github.com/dotnet/roslyn-analyzers diff --git a/global.json b/global.json index 2f53ae5debd80..2d5b35de406e3 100644 --- a/global.json +++ b/global.json @@ -1,18 +1,18 @@ { "sdk": { - "version": "9.0.104", + "version": "9.0.105", "allowPrerelease": false, "rollForward": "patch" }, "tools": { - "dotnet": "9.0.104", + "dotnet": "9.0.105", "vs": { "version": "17.8.0" } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25204.5", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25204.5", + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25208.6", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25208.6", "Microsoft.Build.Traversal": "3.4.0" } } From ea1fb1b3e985a3d32f667927e1f4c80c99e647a1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 12 Apr 2025 12:02:40 +1000 Subject: [PATCH 002/114] Move UIContextActivationService down to features, so the dynamic reg service can be moved --- .../Portable}/Shared/Utilities/IUIContextActivationService.cs | 2 +- .../Implementation/VisualStudioUIContextActivationService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{EditorFeatures/Core => Features/Core/Portable}/Shared/Utilities/IUIContextActivationService.cs (89%) diff --git a/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs b/src/Features/Core/Portable/Shared/Utilities/IUIContextActivationService.cs similarity index 89% rename from src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs rename to src/Features/Core/Portable/Shared/Utilities/IUIContextActivationService.cs index 2b93cdb11512c..d35e164c74044 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs +++ b/src/Features/Core/Portable/Shared/Utilities/IUIContextActivationService.cs @@ -4,7 +4,7 @@ using System; -namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; +namespace Microsoft.CodeAnalysis.Shared.Utilities; internal interface IUIContextActivationService { diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs index 63fd2e3236b9a..47460bb45d75f 100644 --- a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs @@ -5,7 +5,7 @@ using System; using System.Composition; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Shell; From 7c828093fffaef363ccadcc6bc08508b07f6465f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 12 Apr 2025 12:03:13 +1000 Subject: [PATCH 003/114] Move dynamic registration service down to features, and expose to VS and VS Code --- .../Cohost/RazorDynamicRegistrationServiceFactory.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename src/Tools/ExternalAccess/Razor/{EditorFeatures => Features}/Cohost/RazorDynamicRegistrationServiceFactory.cs (90%) diff --git a/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs similarity index 90% rename from src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/RazorDynamicRegistrationServiceFactory.cs rename to src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs index 398ab41cf682b..fc83246d78021 100644 --- a/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -7,7 +7,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] +[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.Any), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class RazorDynamicRegistrationServiceFactory( @@ -48,6 +48,12 @@ public void Dispose() public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { + if (context.ServerKind is not (WellKnownLspServerKinds.AlwaysActiveVSLspServer or WellKnownLspServerKinds.CSharpVisualBasicLspServer)) + { + // We have to register this class for Any server, but only want to run in the C# server in VS or VS Code + return Task.CompletedTask; + } + if (dynamicRegistrationService is null || clientLanguageServerManager is null) { return Task.CompletedTask; From a4c8e4ee7477d28f41a114e38db196be70fdbed6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 14 Apr 2025 09:46:44 +1000 Subject: [PATCH 004/114] Allow semantic tokens refresh queue to run in VS Code or VS --- .../Features/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs index f91bded725875..09c4cabc17d9d 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; @@ -14,7 +15,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -[ExportCohostLspServiceFactory(typeof(IRazorSemanticTokensRefreshQueue)), Shared] +[ExportRazorLspServiceFactory(typeof(IRazorSemanticTokensRefreshQueue)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class RazorSemanticTokensRefreshQueueWrapperFactory() : ILspServiceFactory From 92cc8ef32c2c97d2630f22abc62416da33c21be6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 11 Apr 2025 17:13:59 -0700 Subject: [PATCH 005/114] Initial stubs --- eng/targets/Services.props | 1 + .../Collections/ImmutableArrayExtensions.cs | 3 + .../Copilot/CopilotWpfTextCreationListener.cs | 128 ++++++++++++++++++ .../Portable/Copilot/CopilotChangeAnalysis.cs | 49 +++++++ .../Copilot/ICopilotChangeAnalysisService.cs | 88 ++++++++++++ .../IRemoteCopilotChangeAnalysisService.cs | 19 +++ .../Core/Portable/CodeActions/CodeAction.cs | 1 + .../Shared/TestHooks/FeatureAttribute.cs | 1 + .../Remote/InProcRemostHostClient.cs | 1 + .../Remote/Core/ServiceDescriptors.cs | 2 + .../RemoteCopilotChangeAnalysisService.cs | 40 ++++++ .../RemoteSemanticClassificationService.cs | 1 + .../TextSpanMutableIntervalTree.cs | 3 + .../Compiler/Core/Log/FunctionId.cs | 2 + 14 files changed, 339 insertions(+) create mode 100644 src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs create mode 100644 src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs create mode 100644 src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs create mode 100644 src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs diff --git a/eng/targets/Services.props b/eng/targets/Services.props index 409f1b282f9a1..acf686c9e0571 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -16,6 +16,7 @@ + diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index a2c7638b9b25e..b38795916cb7b 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -1295,6 +1295,9 @@ internal static bool SequenceEqual(this ImmutableArray internal static int IndexOf(this ImmutableArray array, T item, IEqualityComparer comparer) => array.IndexOf(item, startIndex: 0, comparer); + internal static bool IsSorted(this ImmutableArray array, Comparison comparison) + => IsSorted(array, Comparer.Create(comparison)); + internal static bool IsSorted(this ImmutableArray array, IComparer? comparer = null) { comparer ??= Comparer.Default; diff --git a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs new file mode 100644 index 0000000000000..56a2ca4f4b68a --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs @@ -0,0 +1,128 @@ +// 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.ComponentModel.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; +using Microsoft.VisualStudio.Language.Proposals; +using Microsoft.VisualStudio.Language.Suggestions; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.Copilot; + +[Export(typeof(IWpfTextViewCreationListener))] +[ContentType(ContentTypeNames.RoslynContentType)] +[TextViewRole(PredefinedTextViewRoles.Document)] +internal sealed class CopilotWpfTextViewCreationListener : IWpfTextViewCreationListener +{ + private readonly IThreadingContext _threadingContext; + private readonly Lazy _suggestionServiceBase; + + private readonly AsyncBatchingWorkQueue _workQueue; + + private int _started; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CopilotWpfTextViewCreationListener( + IThreadingContext threadingContext, + Lazy suggestionServiceBase, + IAsynchronousOperationListenerProvider listenerProvider) + { + _threadingContext = threadingContext; + _suggestionServiceBase = suggestionServiceBase; + _workQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.Idle, + ProcessEventsAsync, + listenerProvider.GetListener(FeatureAttribute.CopilotChangeAnalysis), + _threadingContext.DisposalToken); + } + + public void TextViewCreated(IWpfTextView textView) + { + // On the first roslyn text view created, kick off work to hydrate the suggestion service and register to events + // from it. + if (Interlocked.CompareExchange(ref _started, 1, 0) == 0) + { + _ = Task.Run(() => + { + var suggestionService = _suggestionServiceBase.Value; + suggestionService.SuggestionAccepted += OnSuggestionAccepted; + }); + } + } + + private void OnSuggestionAccepted(object sender, SuggestionAcceptedEventArgs e) + { + if (e.FinalProposal.Edits.Count == 0) + return; + + _workQueue.AddWork(e); + } + + private async ValueTask ProcessEventsAsync( + ImmutableSegmentedList list, CancellationToken cancellationToken) + { + foreach (var eventArgs in list) + await ProcessEventAsync(eventArgs, cancellationToken).ConfigureAwait(false); + } + + private static async ValueTask ProcessEventAsync( + SuggestionAcceptedEventArgs eventArgs, CancellationToken cancellationToken) + { + foreach (var editGroup in eventArgs.FinalProposal.Edits.GroupBy(e => e.Span.Snapshot)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var snapshot = editGroup.Key; + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + + if (document is null) + continue; + + var normalizedEdits = Normalize(editGroup); + if (normalizedEdits.IsDefaultOrEmpty) + continue; + + var changeAnalysisService = document.Project.Solution.Services.GetRequiredService(); + await changeAnalysisService.AnalyzeChangeAsync(document, normalizedEdits, cancellationToken).ConfigureAwait(false); + } + } + + private static ImmutableArray Normalize(IEnumerable editGroup) + { + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var builder); + foreach (var edit in editGroup) + builder.Add(new TextChange(edit.Span.Span.ToTextSpan(), edit.ReplacementText)); + + // Ensure everything is sorted. + builder.Sort(static (c1, c2) => c1.Span.Start - c2.Span.Start); + + // Now, go through and make sure no edit overlaps another. + for (int i = 1, n = builder.Count; i < n; i++) + { + var lastEdit = builder[i - 1]; + var currentEdit = builder[i]; + + if (lastEdit.Span.OverlapsWith(currentEdit.Span)) + return default; + } + + // Things look good. Can process these sorted edits. + return builder.ToImmutableAndClear(); + } +} diff --git a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs new file mode 100644 index 0000000000000..5b963517f90cf --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs @@ -0,0 +1,49 @@ +// 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.Runtime.Serialization; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Copilot; + +/// Total time to do all analysis (including diagnostics, code fixes, and application). +/// Total time to do all diagnostic computation over all diagnostic kinds. +[DataContract] +internal readonly record struct CopilotChangeAnalysis( + [property: DataMember(Order = 0)] bool Succeeded, + [property: DataMember(Order = 1)] TimeSpan TotalAnalysisTime, + [property: DataMember(Order = 2)] TimeSpan TotalDiagnosticComputationTime, + [property: DataMember(Order = 3)] ImmutableArray DiagnosticAnalyses, + [property: DataMember(Order = 4)] CopilotCodeFixAnalysis CodeFixAnalysis); + +/// What diagnostic kind this is analysis data for. +/// How long it took to produce the diagnostics for this diagnostic kind. +/// Mapping from to the number of diagnostics produced for that id. +/// Mapping from to the number of diagnostics produced for that category. +/// Mapping from to the number of diagnostics produced for that severity. +[DataContract] +internal readonly record struct CopilotDiagnosticAnalysis( + [property: DataMember(Order = 0)] DiagnosticKind Kind, + [property: DataMember(Order = 1)] TimeSpan ComputationTime, + [property: DataMember(Order = 2)] Dictionary IdToCount, + [property: DataMember(Order = 3)] Dictionary CategoryToCount, + [property: DataMember(Order = 4)] Dictionary SeverityToCount); + +/// Total time to compute code fixes for the changed regions. +/// Total time to apply code fixes for the changed regions. +/// Mapping from diagnostic id to to how many diagnostics with that id had fixes. +/// Mapping from diagnostic id to the total time taken to fix diagnostics with that id. +/// Mapping from diagnostic id to the name of the provider that provided the fix. +/// Mapping from provider name to the total time taken to fix diagnostics with that provider. +[DataContract] +internal readonly record struct CopilotCodeFixAnalysis( + [property: DataMember(Order = 0)] TimeSpan TotalComputationTime, + [property: DataMember(Order = 1)] TimeSpan TotalApplicationTime, + [property: DataMember(Order = 2)] Dictionary DiagnosticIdToCount, + [property: DataMember(Order = 3)] Dictionary DiagnosticIdToApplicationTime, + [property: DataMember(Order = 4)] Dictionary> DiagnosticIdToProviderName, + [property: DataMember(Order = 5)] Dictionary ProviderNameToApplicationTime); diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..a78e52f69dead --- /dev/null +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -0,0 +1,88 @@ +// 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.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal interface ICopilotChangeAnalysisService : IWorkspaceService +{ + /// + /// Kicks of work to analyze a change that copilot suggested making to a document. is + /// the state of the document prior to the edits, and are the changes Copilot wants to + /// make to it. must be sorted and normalized before calling this. + /// + Task AnalyzeChangeAsync(Document document, ImmutableArray changes, CancellationToken cancellationToken); +} + +[ExportWorkspaceServiceFactory(typeof(ICopilotChangeAnalysisService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultCopilotChangeAnalysisServiceFactory( + ICodeFixService codeFixService, + IDiagnosticAnalyzerService diagnosticAnalyzerService) : IWorkspaceServiceFactory +{ + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new DefaultCopilotChangeAnalysisService(codeFixService, diagnosticAnalyzerService, workspaceServices); + + private sealed class DefaultCopilotChangeAnalysisService( + ICodeFixService codeFixService, + IDiagnosticAnalyzerService diagnosticAnalyzerService, + HostWorkspaceServices workspaceServices) : ICopilotChangeAnalysisService + { + private readonly ICodeFixService _codeFixService = codeFixService; + private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; + private readonly HostWorkspaceServices _workspaceServices = workspaceServices; + + public async Task AnalyzeChangeAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + if (!document.SupportsSemanticModel) + return default; + + Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); + Contract.ThrowIfTrue(new NormalizedTextSpanCollection(changes.Select(c => c.Span)).Count != changes.Length, "'changes' was not normalized."); + Contract.ThrowIfTrue(document.Project.Solution.Workspace != _workspaceServices.Workspace); + + var client = await RemoteHostClient.TryGetClientAsync( + _workspaceServices.Workspace, cancellationToken).ConfigureAwait(false); + + if (client != null) + { + var value = await client.TryInvokeAsync( + // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. + document.Project, + (service, checksum, cancellationToken) => service.AnalyzeChangeAsync(checksum, document.Id, changes, cancellationToken), + cancellationToken).ConfigureAwait(false); + return value.HasValue ? value.Value : default; + } + else + { + return await AnalyzeChangeInCurrentProcessAsync(document, changes, cancellationToken).ConfigureAwait(false); + } + } + + private async Task AnalyzeChangeInCurrentProcessAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + return default; + } + } +} diff --git a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..185b9de3eb944 --- /dev/null +++ b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Copilot; + +/// Remote version of +internal interface IRemoteCopilotChangeAnalysisService : IWorkspaceService +{ + /// + ValueTask AnalyzeChangeAsync( + Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index dbd9f775ca9d0..644c85de0fd18 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeCleanup; diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 343deb91c658c..1d19cac5f4bd5 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -20,6 +20,7 @@ internal static class FeatureAttribute public const string CompletionSet = nameof(CompletionSet); public const string CopilotImplementNotImplementedException = nameof(CopilotImplementNotImplementedException); public const string CopilotSuggestions = nameof(CopilotSuggestions); + public const string CopilotChangeAnalysis = nameof(CopilotChangeAnalysis); public const string DesignerAttributes = nameof(DesignerAttributes); public const string DiagnosticService = nameof(DiagnosticService); public const string DocumentOutline = nameof(DocumentOutline); diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 6800cd6da60b2..10a4b221077c8 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -182,6 +182,7 @@ public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? t RegisterRemoteBrokeredService(new RemoteAsynchronousOperationListenerService.Factory()); RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); RegisterRemoteBrokeredService(new RemoteConvertTupleToStructCodeRefactoringService.Factory()); + RegisterRemoteBrokeredService(new RemoteCopilotChangeAnalysisService.Factory()); RegisterRemoteBrokeredService(new RemoteDependentTypeFinderService.Factory()); RegisterRemoteBrokeredService(new RemoteDesignerAttributeDiscoveryService.Factory()); RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index 376fe434d2b9f..30daa9b5dcf3c 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeLens; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.ConvertTupleToStruct; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.DesignerAttribute; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.DocumentHighlighting; @@ -58,6 +59,7 @@ internal sealed class ServiceDescriptors (typeof(IRemoteAsynchronousOperationListenerService), null), (typeof(IRemoteCodeLensReferencesService), null), (typeof(IRemoteConvertTupleToStructCodeRefactoringService), null), + (typeof(IRemoteCopilotChangeAnalysisService), null), (typeof(IRemoteDependentTypeFinderService), null), (typeof(IRemoteDesignerAttributeDiscoveryService), typeof(IRemoteDesignerAttributeDiscoveryService.ICallback)), (typeof(IRemoteDiagnosticAnalyzerService), null), diff --git a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..b0e025735b9c7 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed partial class RemoteCopilotChangeAnalysisService( + in BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(arguments), IRemoteCopilotChangeAnalysisService +{ + internal sealed class Factory : FactoryBase + { + protected override IRemoteCopilotChangeAnalysisService CreateService(in ServiceConstructionArguments arguments) + => new RemoteCopilotChangeAnalysisService(arguments); + } + + public ValueTask AnalyzeChangeAsync( + Checksum solutionChecksum, + DocumentId documentId, + ImmutableArray edits, + CancellationToken cancellationToken) + { + return RunServiceAsync(solutionChecksum, async solution => + { + var document = await solution.GetRequiredDocumentAsync( + documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + var service = solution.Services.GetRequiredService(); + await service.AnalyzeChangeAsync( + document, edits, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index 6053c02cd7338..d66319d166ea5 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs index 0c8647b4e755f..4907f3af8e306 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs @@ -26,4 +26,7 @@ public TextSpanMutableIntervalTree(params TextSpan[]? values) : this((IEnumerabl public bool HasIntervalThatIntersectsWith(TextSpan span) => this.HasIntervalThatIntersectsWith(span.Start, span.Length); + + public bool HasIntervalThatOverlapsWith(TextSpan span) + => this.HasIntervalThatOverlapsWith(span.Start, span.Length); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 8c37a0ce21abc..b9ccc9a4b2e3a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -647,6 +647,8 @@ internal enum FunctionId Copilot_Rename = 851, + Copilot_AnalyzeChange = 860, + VSCode_LanguageServer_Started = 860, VSCode_Project_Load_Started = 861, VSCode_Projects_Load_Completed = 862, From 52c3a04798359d7aebd67d83881c0974eee25340 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 09:17:28 -0700 Subject: [PATCH 006/114] Async token --- .../Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs | 9 ++++++--- .../Portable/Copilot/ICopilotChangeAnalysisService.cs | 8 ++++++++ src/Workspaces/Core/Portable/CodeActions/CodeAction.cs | 1 - .../RemoteSemanticClassificationService.cs | 1 - 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs index 56a2ca4f4b68a..6fb682b26acda 100644 --- a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs +++ b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs @@ -31,6 +31,7 @@ internal sealed class CopilotWpfTextViewCreationListener : IWpfTextViewCreationL { private readonly IThreadingContext _threadingContext; private readonly Lazy _suggestionServiceBase; + private readonly IAsynchronousOperationListener _listener; private readonly AsyncBatchingWorkQueue _workQueue; @@ -45,10 +46,11 @@ public CopilotWpfTextViewCreationListener( { _threadingContext = threadingContext; _suggestionServiceBase = suggestionServiceBase; + _listener = listenerProvider.GetListener(FeatureAttribute.CopilotChangeAnalysis); _workQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.Idle, ProcessEventsAsync, - listenerProvider.GetListener(FeatureAttribute.CopilotChangeAnalysis), + _listener, _threadingContext.DisposalToken); } @@ -58,11 +60,12 @@ public void TextViewCreated(IWpfTextView textView) // from it. if (Interlocked.CompareExchange(ref _started, 1, 0) == 0) { - _ = Task.Run(() => + var token = _listener.BeginAsyncOperation(nameof(TextViewCreated)); + Task.Run(() => { var suggestionService = _suggestionServiceBase.Value; suggestionService.SuggestionAccepted += OnSuggestionAccepted; - }); + }).CompletesAsyncOperation(token); } } diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index a78e52f69dead..df068499b07c7 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -43,8 +43,10 @@ private sealed class DefaultCopilotChangeAnalysisService( IDiagnosticAnalyzerService diagnosticAnalyzerService, HostWorkspaceServices workspaceServices) : ICopilotChangeAnalysisService { +#pragma warning disable IDE0052 // Remove unread private members private readonly ICodeFixService _codeFixService = codeFixService; private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; +#pragma warning restore IDE0052 // Remove unread private members private readonly HostWorkspaceServices _workspaceServices = workspaceServices; public async Task AnalyzeChangeAsync( @@ -77,6 +79,9 @@ public async Task AnalyzeChangeAsync( } } +#pragma warning disable CA1822 // Mark members as static +#pragma warning disable IDE0060 // Remove unused parameter +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private async Task AnalyzeChangeInCurrentProcessAsync( Document document, ImmutableArray changes, @@ -84,5 +89,8 @@ private async Task AnalyzeChangeInCurrentProcessAsync( { return default; } +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning restore IDE0060 // Remove unused parameter +#pragma warning restore CA1822 // Mark members as static } } diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 644c85de0fd18..dbd9f775ca9d0 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -8,7 +8,6 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeCleanup; diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index d66319d166ea5..6053c02cd7338 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; From 4478ccafd6795327122f4c31f8ed4ae68be7237c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 21:40:23 -0700 Subject: [PATCH 007/114] Change value --- .../Compiler/Core/Log/FunctionId.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index b9ccc9a4b2e3a..2af501ba1e198 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -645,9 +645,9 @@ internal enum FunctionId Copilot_Implement_NotImplementedException_Failed = 831, Copilot_Implement_NotImplementedException_Completed = 832, - Copilot_Rename = 851, + Copilot_AnalyzeChange = 840, - Copilot_AnalyzeChange = 860, + Copilot_Rename = 851, VSCode_LanguageServer_Started = 860, VSCode_Project_Load_Started = 861, From 33583690fbad03e88c58c7847093e6e3b866049d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 21:41:07 -0700 Subject: [PATCH 008/114] Fix --- .../Services/Copilot/RemoteCopilotChangeAnalysisService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs index b0e025735b9c7..627a0587d070f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs @@ -21,7 +21,7 @@ protected override IRemoteCopilotChangeAnalysisService CreateService(in ServiceC => new RemoteCopilotChangeAnalysisService(arguments); } - public ValueTask AnalyzeChangeAsync( + public ValueTask AnalyzeChangeAsync( Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, @@ -33,7 +33,7 @@ public ValueTask AnalyzeChangeAsync( documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); var service = solution.Services.GetRequiredService(); - await service.AnalyzeChangeAsync( + return await service.AnalyzeChangeAsync( document, edits, cancellationToken).ConfigureAwait(false); }, cancellationToken); } From 55806276e06245aea488871f365a02d613a54502 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 22:58:32 -0700 Subject: [PATCH 009/114] Allow null --- .../Copilot/ICopilotChangeAnalysisService.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index df068499b07c7..1ef66c2d44376 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -32,20 +32,20 @@ internal interface ICopilotChangeAnalysisService : IWorkspaceService [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultCopilotChangeAnalysisServiceFactory( - ICodeFixService codeFixService, - IDiagnosticAnalyzerService diagnosticAnalyzerService) : IWorkspaceServiceFactory + [Import(AllowDefault = true)] ICodeFixService? codeFixService, + [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService) : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => new DefaultCopilotChangeAnalysisService(codeFixService, diagnosticAnalyzerService, workspaceServices); private sealed class DefaultCopilotChangeAnalysisService( - ICodeFixService codeFixService, - IDiagnosticAnalyzerService diagnosticAnalyzerService, + ICodeFixService? codeFixService, + IDiagnosticAnalyzerService? diagnosticAnalyzerService, HostWorkspaceServices workspaceServices) : ICopilotChangeAnalysisService { #pragma warning disable IDE0052 // Remove unread private members - private readonly ICodeFixService _codeFixService = codeFixService; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; + private readonly ICodeFixService? _codeFixService = codeFixService; + private readonly IDiagnosticAnalyzerService? _diagnosticAnalyzerService = diagnosticAnalyzerService; #pragma warning restore IDE0052 // Remove unread private members private readonly HostWorkspaceServices _workspaceServices = workspaceServices; @@ -54,7 +54,7 @@ public async Task AnalyzeChangeAsync( ImmutableArray changes, CancellationToken cancellationToken) { - if (!document.SupportsSemanticModel) + if (_codeFixService is null || _diagnosticAnalyzerService is null || !document.SupportsSemanticModel) return default; Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); From f596a128351e5ee2eb249893c14247b849cea1dd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 23:15:47 -0700 Subject: [PATCH 010/114] Simplify --- .../Core/Portable/Copilot/ICopilotChangeAnalysisService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index 1ef66c2d44376..250a76715119f 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -54,7 +54,7 @@ public async Task AnalyzeChangeAsync( ImmutableArray changes, CancellationToken cancellationToken) { - if (_codeFixService is null || _diagnosticAnalyzerService is null || !document.SupportsSemanticModel) + if (!document.SupportsSemanticModel) return default; Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); From a0191e80ca5aa811e04ff64307a980de1331b686 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 23:17:33 -0700 Subject: [PATCH 011/114] Simplufy --- .../Copilot/ICopilotChangeAnalysisService.cs | 82 ++++++++----------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index 250a76715119f..885791b9e0120 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -28,69 +28,57 @@ internal interface ICopilotChangeAnalysisService : IWorkspaceService Task AnalyzeChangeAsync(Document document, ImmutableArray changes, CancellationToken cancellationToken); } -[ExportWorkspaceServiceFactory(typeof(ICopilotChangeAnalysisService)), Shared] +[ExportWorkspaceService(typeof(ICopilotChangeAnalysisService)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DefaultCopilotChangeAnalysisServiceFactory( +internal sealed class DefaultCopilotChangeAnalysisService( [Import(AllowDefault = true)] ICodeFixService? codeFixService, - [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService) : IWorkspaceServiceFactory + [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService) : ICopilotChangeAnalysisService { - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new DefaultCopilotChangeAnalysisService(codeFixService, diagnosticAnalyzerService, workspaceServices); - - private sealed class DefaultCopilotChangeAnalysisService( - ICodeFixService? codeFixService, - IDiagnosticAnalyzerService? diagnosticAnalyzerService, - HostWorkspaceServices workspaceServices) : ICopilotChangeAnalysisService - { #pragma warning disable IDE0052 // Remove unread private members - private readonly ICodeFixService? _codeFixService = codeFixService; - private readonly IDiagnosticAnalyzerService? _diagnosticAnalyzerService = diagnosticAnalyzerService; + private readonly ICodeFixService? _codeFixService = codeFixService; + private readonly IDiagnosticAnalyzerService? _diagnosticAnalyzerService = diagnosticAnalyzerService; #pragma warning restore IDE0052 // Remove unread private members - private readonly HostWorkspaceServices _workspaceServices = workspaceServices; - public async Task AnalyzeChangeAsync( - Document document, - ImmutableArray changes, - CancellationToken cancellationToken) - { - if (!document.SupportsSemanticModel) - return default; + public async Task AnalyzeChangeAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + if (!document.SupportsSemanticModel) + return default; - Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); - Contract.ThrowIfTrue(new NormalizedTextSpanCollection(changes.Select(c => c.Span)).Count != changes.Length, "'changes' was not normalized."); - Contract.ThrowIfTrue(document.Project.Solution.Workspace != _workspaceServices.Workspace); + Contract.ThrowIfTrue(!changes.IsSorted(static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); + Contract.ThrowIfTrue(new NormalizedTextSpanCollection(changes.Select(c => c.Span)).Count != changes.Length, "'changes' was not normalized."); - var client = await RemoteHostClient.TryGetClientAsync( - _workspaceServices.Workspace, cancellationToken).ConfigureAwait(false); + var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); - if (client != null) - { - var value = await client.TryInvokeAsync( - // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. - document.Project, - (service, checksum, cancellationToken) => service.AnalyzeChangeAsync(checksum, document.Id, changes, cancellationToken), - cancellationToken).ConfigureAwait(false); - return value.HasValue ? value.Value : default; - } - else - { - return await AnalyzeChangeInCurrentProcessAsync(document, changes, cancellationToken).ConfigureAwait(false); - } + if (client != null) + { + var value = await client.TryInvokeAsync( + // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. + document.Project, + (service, checksum, cancellationToken) => service.AnalyzeChangeAsync(checksum, document.Id, changes, cancellationToken), + cancellationToken).ConfigureAwait(false); + return value.HasValue ? value.Value : default; + } + else + { + return await AnalyzeChangeInCurrentProcessAsync(document, changes, cancellationToken).ConfigureAwait(false); } + } #pragma warning disable CA1822 // Mark members as static #pragma warning disable IDE0060 // Remove unused parameter #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - private async Task AnalyzeChangeInCurrentProcessAsync( - Document document, - ImmutableArray changes, - CancellationToken cancellationToken) - { - return default; - } + private async Task AnalyzeChangeInCurrentProcessAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + return default; + } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore IDE0060 // Remove unused parameter #pragma warning restore CA1822 // Mark members as static - } } From ae531dd7c2a9d1cdbdad2af59ec885fd2e10fa09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 14 Apr 2025 23:22:21 -0700 Subject: [PATCH 012/114] Add null --- .../Core/Portable/Copilot/ICopilotChangeAnalysisService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index 885791b9e0120..e7ec1a09dc30c 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -32,8 +32,8 @@ internal interface ICopilotChangeAnalysisService : IWorkspaceService [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultCopilotChangeAnalysisService( - [Import(AllowDefault = true)] ICodeFixService? codeFixService, - [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService) : ICopilotChangeAnalysisService + [Import(AllowDefault = true)] ICodeFixService? codeFixService = null, + [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService = null) : ICopilotChangeAnalysisService { #pragma warning disable IDE0052 // Remove unread private members private readonly ICodeFixService? _codeFixService = codeFixService; From f0276d867f607be95818c39b649e6533b2ff9bbd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 15 Apr 2025 11:54:15 +1000 Subject: [PATCH 013/114] Allow the solution info wrapper to carry a real solution for in-proc purposes --- ...ializableRazorPinnedSolutionInfoWrapper.cs | 7 +++--- .../Remote/RazorPinnedSolutionInfoWrapper.cs | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Remote/JsonSerializableRazorPinnedSolutionInfoWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Remote/JsonSerializableRazorPinnedSolutionInfoWrapper.cs index a248fd113208a..7d88dc338fddd 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Remote/JsonSerializableRazorPinnedSolutionInfoWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Remote/JsonSerializableRazorPinnedSolutionInfoWrapper.cs @@ -11,16 +11,17 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor /// internal readonly record struct JsonSerializableRazorPinnedSolutionInfoWrapper( [property: JsonPropertyName("data1")] long Data1, - [property: JsonPropertyName("data2")] long Data2) + [property: JsonPropertyName("data2")] long Data2, + [property: JsonIgnore] Solution? Solution) { public static implicit operator JsonSerializableRazorPinnedSolutionInfoWrapper(RazorPinnedSolutionInfoWrapper info) { - return new JsonSerializableRazorPinnedSolutionInfoWrapper(info.UnderlyingObject.Data1, info.UnderlyingObject.Data2); + return new JsonSerializableRazorPinnedSolutionInfoWrapper(info.UnderlyingObject.Data1, info.UnderlyingObject.Data2, info.Solution); } public static implicit operator RazorPinnedSolutionInfoWrapper(JsonSerializableRazorPinnedSolutionInfoWrapper serializableDocumentId) { - return new RazorPinnedSolutionInfoWrapper(new Checksum(serializableDocumentId.Data1, serializableDocumentId.Data2)); + return new RazorPinnedSolutionInfoWrapper(new Checksum(serializableDocumentId.Data1, serializableDocumentId.Data2), serializableDocumentId.Solution); } } } diff --git a/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs index eec9f59b0de71..ed8ddac1513a9 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs @@ -2,12 +2,13 @@ // 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.Runtime.Serialization; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { /// - /// A wrapper for a solution that can be used by Razor for OOP services that communicate via MessagePack + /// A wrapper for a solution that can be used by Razor for OOP services that communicate via MessagePack, or in proc services that don't communicate. /// [DataContract] internal readonly struct RazorPinnedSolutionInfoWrapper @@ -15,10 +16,22 @@ internal readonly struct RazorPinnedSolutionInfoWrapper [DataMember(Order = 0)] internal readonly Checksum UnderlyingObject; - public RazorPinnedSolutionInfoWrapper(Checksum underlyingObject) - => UnderlyingObject = underlyingObject; + // Not serialized because it should only be used in in-proc scenarios. + internal readonly Solution? Solution; - public static implicit operator RazorPinnedSolutionInfoWrapper(Checksum info) - => new(info); + internal RazorPinnedSolutionInfoWrapper(Checksum checksum, Solution? solution) + { + Contract.ThrowIfTrue(checksum == Checksum.Null && solution is null, "Either a Checksum or a Solution must be provided."); + Contract.ThrowIfTrue(checksum != Checksum.Null && solution is not null, "Only one of Checksum or Solution can be provided."); + + UnderlyingObject = checksum; + Solution = solution; + } + + public static implicit operator RazorPinnedSolutionInfoWrapper(Checksum checksum) + => new(checksum, null); + + public static implicit operator RazorPinnedSolutionInfoWrapper(Solution solution) + => new(Checksum.Null, solution); } } From b9f976218796b6399bc0bd39c380e1c717ded4f2 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 08:39:44 +1000 Subject: [PATCH 014/114] Formalize cohosting startup into its own thing, not just for dynamic registration --- .../Features/Cohost/ICohostStartupService.cs | 13 +++++++++ .../IRazorCohostDynamicRegistrationService.cs | 2 ++ ...ctory.cs => RazorStartupServiceFactory.cs} | 27 ++++++++++++------- 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs rename src/Tools/ExternalAccess/Razor/Features/Cohost/{RazorDynamicRegistrationServiceFactory.cs => RazorStartupServiceFactory.cs} (78%) diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs new file mode 100644 index 0000000000000..d8e01a448a05c --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/ICohostStartupService.cs @@ -0,0 +1,13 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +internal interface ICohostStartupService +{ + Task StartupAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs index 94156a79011a4..c21bc637df708 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs @@ -2,11 +2,13 @@ // 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.Threading; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; +[Obsolete("Use ICohostStartupService instead")] internal interface IRazorCohostDynamicRegistrationService { Task RegisterAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs similarity index 78% rename from src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs rename to src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs index fc83246d78021..a9620fa8a14d0 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs @@ -20,21 +20,22 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.Any), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicRegistrationServiceFactory( +internal sealed class RazorStartupServiceFactory( [Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService, - [Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory + [Import(AllowDefault = true)] Lazy? dynamicRegistrationService, + [Import(AllowDefault = true)] Lazy? cohostStartupService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { - var clientLanguageServerManager = lspServices.GetRequiredService(); - - return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, clientLanguageServerManager); + return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, cohostStartupService); } private class RazorDynamicRegistrationService( IUIContextActivationService? uIContextActivationService, +#pragma warning disable CS0618 // Type or member is obsolete Lazy? dynamicRegistrationService, - IClientLanguageServerManager? clientLanguageServerManager) : ILspService, IOnInitialized, IDisposable +#pragma warning restore CS0618 // Type or member is obsolete + Lazy? cohostStartupService) : ILspService, IOnInitialized, IDisposable { private readonly CancellationTokenSource _disposalTokenSource = new(); private IDisposable? _activation; @@ -54,7 +55,7 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon return Task.CompletedTask; } - if (dynamicRegistrationService is null || clientLanguageServerManager is null) + if (dynamicRegistrationService is null && cohostStartupService is null) { return Task.CompletedTask; } @@ -88,10 +89,18 @@ private async Task InitializeRazorAsync(ClientCapabilities clientCapabilities, R // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL var serializedClientCapabilities = JsonSerializer.Serialize(clientCapabilities, ProtocolConversions.LspJsonSerializerOptions); - var razorCohostClientLanguageServerManager = new RazorClientLanguageServerManager(clientLanguageServerManager!); var requestContext = new RazorCohostRequestContext(context); - await dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); + + if (cohostStartupService is not null) + { + await cohostStartupService.Value.StartupAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); + } + + if (dynamicRegistrationService is not null) + { + await dynamicRegistrationService.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); + } } } } From e2d875aab3bdc4c161e9b5bd75f95fe9411dac49 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 08:45:32 +1000 Subject: [PATCH 015/114] Sync option for source generator execution to the LSP server --- .../DidChangeConfigurationNotificationHandler.cs | 5 +++-- .../DidChangeConfigurationNotificationHandler_OptionList.cs | 2 ++ .../DidChangeConfigurationNotificationHandlerTest.cs | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs index 2f988beec8053..0a98efa278e31 100644 --- a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs @@ -187,9 +187,10 @@ internal static string GenerateFullNameForOption(IOption2 option) { var optionGroupName = GenerateOptionGroupName(option); // All options send to the client should have group name and config name. - RoslynDebug.Assert(!string.IsNullOrEmpty(optionGroupName)); RoslynDebug.Assert(!string.IsNullOrEmpty(option.Definition.ConfigName)); - return string.Concat(optionGroupName, '.', option.Definition.ConfigName); + return string.IsNullOrEmpty(optionGroupName) + ? option.Definition.ConfigName + : string.Concat(optionGroupName, '.', option.Definition.ConfigName); } private static string GenerateOptionGroupName(IOption2 option) diff --git a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs index e9ffae70be1f0..763b97055a27b 100644 --- a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs +++ b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.InlineHints; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -59,5 +60,6 @@ internal sealed partial class DidChangeConfigurationNotificationHandler LanguageServerProjectSystemOptionsStorage.EnableAutomaticRestore, MetadataAsSourceOptionsStorage.NavigateToSourceLinkAndEmbeddedSources, LspOptionsStorage.LspOrganizeImportsOnFormat, + WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, // Temporarily added to enable Razor cohosting dogfooding. Not exposed in the VS Code UI ]; } diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index e0afe76a84119..131d75a0b7d31 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -149,6 +149,7 @@ public void VerifyLspClientOptionNames() "projects.dotnet_enable_automatic_restore", "navigation.dotnet_navigate_to_source_link_and_embedded_sources", "formatting.dotnet_organize_imports_on_format", + "dotnet_source_generator_execution", }; AssertEx.EqualOrDiff( From b134cfc58e55e21387cbe1e89243b90de349b45e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 13:39:34 +1000 Subject: [PATCH 016/114] Fix deserialization when calling OOP --- .../Features/Remote/RazorPinnedSolutionInfoWrapper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs index ed8ddac1513a9..ea2879ad904a6 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Remote/RazorPinnedSolutionInfoWrapper.cs @@ -2,7 +2,6 @@ // 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.Runtime.Serialization; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor @@ -19,6 +18,12 @@ internal readonly struct RazorPinnedSolutionInfoWrapper // Not serialized because it should only be used in in-proc scenarios. internal readonly Solution? Solution; + // Needed for the message pack formatter to work + internal RazorPinnedSolutionInfoWrapper(Checksum checksum) + : this(checksum, null) + { + } + internal RazorPinnedSolutionInfoWrapper(Checksum checksum, Solution? solution) { Contract.ThrowIfTrue(checksum == Checksum.Null && solution is null, "Either a Checksum or a Solution must be provided."); From 2f53514ae3f51ef2df4f000e82c381bbf64990f9 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 13:39:49 +1000 Subject: [PATCH 017/114] Fix Razor EA publishing, and add it to the MEF composition --- eng/config/PublishData.json | 1 - .../Microsoft.CodeAnalysis.CSharp.Features.csproj | 1 - .../Core/Portable/Microsoft.CodeAnalysis.Features.csproj | 1 - src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj | 8 +++++++- src/VisualStudio/Setup/source.extension.vsixmanifest | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index e2f793dbe83be..b6ab9ae09b4a7 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -78,7 +78,6 @@ "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.AspNetCore": "vs-impl", - "Microsoft.CodeAnalysis.ExternalAccess.Razor": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Razor.EditorFeatures": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Razor.Features": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler": "vs-impl", diff --git a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj index 29bad84922417..0b4b4acdfff34 100644 --- a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj +++ b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj @@ -35,7 +35,6 @@ - diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index a017065103faf..b934fe1000d94 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -48,7 +48,6 @@ - diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index bef9fd44f42fc..d0bc795b89294 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -79,8 +79,14 @@ true BindingRedirect + + Microsoft.CodeAnalysis.ExternalAccess.Razor.Features + BuiltProjectOutputGroup + true + BindingRedirect + - Microsoft.CodeAnalysis.ExternalAccess.Razor + Microsoft.CodeAnalysis.ExternalAccess.Razor.EditorFeatures BuiltProjectOutputGroup true BindingRedirect diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest index add0ea3be74b0..1de9b1ffa436c 100644 --- a/src/VisualStudio/Setup/source.extension.vsixmanifest +++ b/src/VisualStudio/Setup/source.extension.vsixmanifest @@ -63,7 +63,8 @@ - + + From 3fd57963c62a7f7e934a2125a0788f6662b60780 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 13:49:02 +1000 Subject: [PATCH 018/114] Rename --- .../Razor/Features/Cohost/RazorStartupServiceFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs index a9620fa8a14d0..35563e980fa39 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.Any), Shared] +[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorStartupService), WellKnownLspServerKinds.Any), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class RazorStartupServiceFactory( @@ -27,10 +27,10 @@ internal sealed class RazorStartupServiceFactory( { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { - return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, cohostStartupService); + return new RazorStartupService(uIContextActivationService, dynamicRegistrationService, cohostStartupService); } - private class RazorDynamicRegistrationService( + private class RazorStartupService( IUIContextActivationService? uIContextActivationService, #pragma warning disable CS0618 // Type or member is obsolete Lazy? dynamicRegistrationService, From 5d51c96b7d5c8971d4e11239bb35d0b001922c51 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 15:27:26 +1000 Subject: [PATCH 019/114] Don't append ServiceHubCore folder name if there isn't one --- .../Razor/Features/RazorAnalyzerAssemblyResolver.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorAnalyzerAssemblyResolver.cs b/src/Tools/ExternalAccess/Razor/Features/RazorAnalyzerAssemblyResolver.cs index 021e443098c2e..0470c37e341d8 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorAnalyzerAssemblyResolver.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorAnalyzerAssemblyResolver.cs @@ -63,7 +63,13 @@ internal sealed class RazorAnalyzerAssemblyResolver() : IAnalyzerAssemblyResolve // runs in a .net based host, it's safe to always choose the .net targeted ServiceHubCore versions. if (!Path.GetFileName(directory.AsSpan().TrimEnd(Path.DirectorySeparatorChar)).Equals(ServiceHubCoreFolderName, StringComparison.OrdinalIgnoreCase)) { - directory = Path.Combine(directory, ServiceHubCoreFolderName); + var serviceHubCoreDirectory = Path.Combine(directory, ServiceHubCoreFolderName); + + // The logic above only applies to VS. In VS Code there is no service hub, so appending the folder would be silly. + if (Directory.Exists(serviceHubCoreDirectory)) + { + directory = serviceHubCoreDirectory; + } } var assemblyPath = Path.Combine(directory, assemblyFileName); From 761f27ad7be76162dc201e758617c3211eafa6b4 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 16 Apr 2025 18:40:31 +1000 Subject: [PATCH 020/114] Update eng/config/PublishData.json --- eng/config/PublishData.json | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index b6ab9ae09b4a7..e2f793dbe83be 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -78,6 +78,7 @@ "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp": "arcade", "Microsoft.CodeAnalysis.ExternalAccess.AspNetCore": "vs-impl", + "Microsoft.CodeAnalysis.ExternalAccess.Razor": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Razor.EditorFeatures": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Razor.Features": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler": "vs-impl", From aedead6cacedbdf0d481696f8e73725f696716d3 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 17 Apr 2025 07:36:19 +1000 Subject: [PATCH 021/114] Don't need this setting any more --- .../DidChangeConfigurationNotificationHandler.cs | 5 ++--- .../DidChangeConfigurationNotificationHandler_OptionList.cs | 1 - .../DidChangeConfigurationNotificationHandlerTest.cs | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs index 0a98efa278e31..2f988beec8053 100644 --- a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs @@ -187,10 +187,9 @@ internal static string GenerateFullNameForOption(IOption2 option) { var optionGroupName = GenerateOptionGroupName(option); // All options send to the client should have group name and config name. + RoslynDebug.Assert(!string.IsNullOrEmpty(optionGroupName)); RoslynDebug.Assert(!string.IsNullOrEmpty(option.Definition.ConfigName)); - return string.IsNullOrEmpty(optionGroupName) - ? option.Definition.ConfigName - : string.Concat(optionGroupName, '.', option.Definition.ConfigName); + return string.Concat(optionGroupName, '.', option.Definition.ConfigName); } private static string GenerateOptionGroupName(IOption2 option) diff --git a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs index 763b97055a27b..2a9a61828b0b3 100644 --- a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs +++ b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler_OptionList.cs @@ -60,6 +60,5 @@ internal sealed partial class DidChangeConfigurationNotificationHandler LanguageServerProjectSystemOptionsStorage.EnableAutomaticRestore, MetadataAsSourceOptionsStorage.NavigateToSourceLinkAndEmbeddedSources, LspOptionsStorage.LspOrganizeImportsOnFormat, - WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, // Temporarily added to enable Razor cohosting dogfooding. Not exposed in the VS Code UI ]; } diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 131d75a0b7d31..e0afe76a84119 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -149,7 +149,6 @@ public void VerifyLspClientOptionNames() "projects.dotnet_enable_automatic_restore", "navigation.dotnet_navigate_to_source_link_and_embedded_sources", "formatting.dotnet_organize_imports_on_format", - "dotnet_source_generator_execution", }; AssertEx.EqualOrDiff( From f2ab1ae682fdafbded5d2c3c9ebe4034ac884ec5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 17 Apr 2025 12:40:14 -0700 Subject: [PATCH 022/114] Add correlation id --- .../Core/Portable/Copilot/ICopilotChangeAnalysisService.cs | 6 ++++-- .../Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs | 3 ++- .../Services/Copilot/RemoteCopilotChangeAnalysisService.cs | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index e7ec1a09dc30c..ac5acfe4648d3 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -25,7 +25,7 @@ internal interface ICopilotChangeAnalysisService : IWorkspaceService /// the state of the document prior to the edits, and are the changes Copilot wants to /// make to it. must be sorted and normalized before calling this. /// - Task AnalyzeChangeAsync(Document document, ImmutableArray changes, CancellationToken cancellationToken); + Task AnalyzeChangeAsync(Document document, ImmutableArray changes, Guid correlationId, CancellationToken cancellationToken); } [ExportWorkspaceService(typeof(ICopilotChangeAnalysisService)), Shared] @@ -43,6 +43,7 @@ internal sealed class DefaultCopilotChangeAnalysisService( public async Task AnalyzeChangeAsync( Document document, ImmutableArray changes, + Guid correlationId, CancellationToken cancellationToken) { if (!document.SupportsSemanticModel) @@ -58,7 +59,8 @@ public async Task AnalyzeChangeAsync( var value = await client.TryInvokeAsync( // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. document.Project, - (service, checksum, cancellationToken) => service.AnalyzeChangeAsync(checksum, document.Id, changes, cancellationToken), + (service, checksum, cancellationToken) => service.AnalyzeChangeAsync( + checksum, document.Id, changes, correlationId, cancellationToken), cancellationToken).ConfigureAwait(false); return value.HasValue ? value.Value : default; } diff --git a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs index 185b9de3eb944..7034e906631f5 100644 --- a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.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,5 +16,5 @@ internal interface IRemoteCopilotChangeAnalysisService : IWorkspaceService { /// ValueTask AnalyzeChangeAsync( - Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, CancellationToken cancellationToken); + Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, Guid correlationId, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs index 627a0587d070f..a45cabc05ec3d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.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; @@ -25,6 +26,7 @@ public ValueTask AnalyzeChangeAsync( Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, + Guid correlationId, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => @@ -34,7 +36,7 @@ public ValueTask AnalyzeChangeAsync( var service = solution.Services.GetRequiredService(); return await service.AnalyzeChangeAsync( - document, edits, cancellationToken).ConfigureAwait(false); + document, edits, correlationId, cancellationToken).ConfigureAwait(false); }, cancellationToken); } } From b01653773560584babd1c6f441120001c288b5a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 21 Apr 2025 13:51:11 -0700 Subject: [PATCH 023/114] Move to string --- .../Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs | 8 ++++++-- .../Portable/Copilot/ICopilotChangeAnalysisService.cs | 7 ++++--- .../Copilot/IRemoteCopilotChangeAnalysisService.cs | 2 +- .../Copilot/RemoteCopilotChangeAnalysisService.cs | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs index 6fb682b26acda..36da95ece96a7 100644 --- a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs +++ b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs @@ -87,7 +87,10 @@ private async ValueTask ProcessEventsAsync( private static async ValueTask ProcessEventAsync( SuggestionAcceptedEventArgs eventArgs, CancellationToken cancellationToken) { - foreach (var editGroup in eventArgs.FinalProposal.Edits.GroupBy(e => e.Span.Snapshot)) + var proposal = eventArgs.FinalProposal; + var proposalId = proposal.ProposalId; + + foreach (var editGroup in proposal.Edits.GroupBy(e => e.Span.Snapshot)) { cancellationToken.ThrowIfCancellationRequested(); @@ -102,7 +105,8 @@ private static async ValueTask ProcessEventAsync( continue; var changeAnalysisService = document.Project.Solution.Services.GetRequiredService(); - await changeAnalysisService.AnalyzeChangeAsync(document, normalizedEdits, cancellationToken).ConfigureAwait(false); + await changeAnalysisService.AnalyzeChangeAsync( + document, normalizedEdits, proposalId, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index ac5acfe4648d3..fd2849e1dd6d1 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -25,7 +25,8 @@ internal interface ICopilotChangeAnalysisService : IWorkspaceService /// the state of the document prior to the edits, and are the changes Copilot wants to /// make to it. must be sorted and normalized before calling this. /// - Task AnalyzeChangeAsync(Document document, ImmutableArray changes, Guid correlationId, CancellationToken cancellationToken); + Task AnalyzeChangeAsync( + Document document, ImmutableArray changes, string proposalId, CancellationToken cancellationToken); } [ExportWorkspaceService(typeof(ICopilotChangeAnalysisService)), Shared] @@ -43,7 +44,7 @@ internal sealed class DefaultCopilotChangeAnalysisService( public async Task AnalyzeChangeAsync( Document document, ImmutableArray changes, - Guid correlationId, + string proposalId, CancellationToken cancellationToken) { if (!document.SupportsSemanticModel) @@ -60,7 +61,7 @@ public async Task AnalyzeChangeAsync( // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. document.Project, (service, checksum, cancellationToken) => service.AnalyzeChangeAsync( - checksum, document.Id, changes, correlationId, cancellationToken), + checksum, document.Id, changes, proposalId, cancellationToken), cancellationToken).ConfigureAwait(false); return value.HasValue ? value.Value : default; } diff --git a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs index 7034e906631f5..fbb6347b4962b 100644 --- a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs @@ -16,5 +16,5 @@ internal interface IRemoteCopilotChangeAnalysisService : IWorkspaceService { /// ValueTask AnalyzeChangeAsync( - Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, Guid correlationId, CancellationToken cancellationToken); + Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, string proposalId, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs index a45cabc05ec3d..a9e89cbdf36cb 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs @@ -26,7 +26,7 @@ public ValueTask AnalyzeChangeAsync( Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, - Guid correlationId, + string proposalId, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => @@ -36,7 +36,7 @@ public ValueTask AnalyzeChangeAsync( var service = solution.Services.GetRequiredService(); return await service.AnalyzeChangeAsync( - document, edits, correlationId, cancellationToken).ConfigureAwait(false); + document, edits, proposalId, cancellationToken).ConfigureAwait(false); }, cancellationToken); } } From cfbec587c60f01dcff3476687e1f45a8fd960571 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 21 Apr 2025 14:51:33 -0700 Subject: [PATCH 024/114] Catch and report when we are unable to make an invisible editor --- .../Def/ProjectSystem/VisualStudioWorkspaceImpl.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index eef19e6672958..489337b1beab2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1212,8 +1212,14 @@ private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) } // The document wasn't open in a normal way, so invisible editor time - using var invisibleEditor = OpenInvisibleEditor(documentId); - TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); + try + { + using var invisibleEditor = OpenInvisibleEditor(documentId); + TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); + } + catch (System.Runtime.InteropServices.COMException ex) when (FatalError.ReportAndCatch(ex)) + { + } } } From 451f8ac9e9f0dc9d4a2ac882b4785b6ef5989418 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 11:54:39 -0700 Subject: [PATCH 025/114] Report issue using info bar if we can't change a file --- .../VisualStudioWorkspaceImpl.cs | 90 +++++++++++++++---- .../Core/Def/ServicesVSResources.resx | 9 ++ .../Telemetry/TelemetryFeatureName.cs | 7 +- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index 489337b1beab2..cca7bb3d011ed 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -112,6 +113,12 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly IAsynchronousOperationListener _workspaceListener; private bool _isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents; + /// + /// Only read/written on hte UI thread. + /// + private bool _isShowingDocumentChangeErrorInfoBar = false; + private bool _ignoreDocumentTextChangeErrors; + public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServiceProvider asyncServiceProvider) : base(VisualStudioMefHostServices.Create(exportProvider)) { @@ -1192,35 +1199,80 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) { - var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); + this._threadingContext.ThrowIfNotOnUIThread(); - if (containedDocument != null) - { - containedDocument.UpdateText(newText); - } - else + CodeAnalysis.TextDocument? document = null; + + try { - if (IsDocumentOpen(documentId)) - { - var textBuffer = this.CurrentSolution.GetTextDocument(documentId)!.GetTextSynchronously(CancellationToken.None).Container.TryGetTextBuffer(); + document = this.CurrentSolution.GetRequiredTextDocument(documentId); - if (textBuffer != null) + var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); + if (containedDocument != null) + { + containedDocument.UpdateText(newText); + } + else + { + if (IsDocumentOpen(documentId)) { - TextEditApplication.UpdateText(newText, textBuffer, EditOptions.DefaultMinimalChange); - return; + var textBuffer = document.GetTextSynchronously(CancellationToken.None).Container.TryGetTextBuffer(); + if (textBuffer != null) + { + TextEditApplication.UpdateText(newText, textBuffer, EditOptions.DefaultMinimalChange); + return; + } } - } - // The document wasn't open in a normal way, so invisible editor time - try - { + // The document wasn't open in a normal way, so invisible editor time using var invisibleEditor = OpenInvisibleEditor(documentId); TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); } - catch (System.Runtime.InteropServices.COMException ex) when (FatalError.ReportAndCatch(ex)) - { - } } + catch (Exception ex) when (FatalError.ReportAndCatch(ex)) + { + ReportErrorChangingDocumentText(ex); + } + + void ReportErrorChangingDocumentText(Exception exception) + { + // Don't spam the info bar. If the user already has a message up, leave it at that. Also, + // if they've asked to not be notified about future doc change issue, respect that flag. + if (_ignoreDocumentTextChangeErrors || _isShowingDocumentChangeErrorInfoBar) + return; + + var documentName = document?.Name ?? documentId.ToString(); + + var errorReportingService = this.Services.GetRequiredService(); + errorReportingService.ShowGlobalErrorInfo( + message: string.Format(ServicesVSResources.Error_encountered_updating_0, documentName), + TelemetryFeatureName.Workspace, + exception, + // 'Show stack trace' will not dismiss the info bar. + new InfoBarUI(WorkspacesResources.Show_Stack_Trace, InfoBarUI.UIKind.HyperLink, + () => errorReportingService.ShowDetailedErrorInfo(exception), closeAfterAction: false), + // 'Ignore' just closes the info bar, but allows future errors to show up. + new InfoBarUI(ServicesVSResources.Ignore, InfoBarUI.UIKind.Button, GetDefaultDismissAction()), + // 'Ignore (including future errors) closes the info bar, but also sets the flag so the user gets no more messages + // in the current session. + new InfoBarUI(ServicesVSResources.Ignore_including_future_errors, InfoBarUI.UIKind.Button, GetDefaultDismissAction( + () => _ignoreDocumentTextChangeErrors = true)), + // Close button is the same as 'ignore'. It closes the info bar, but allows future errors to show up. + new InfoBarUI(string.Empty, InfoBarUI.UIKind.Close, GetDefaultDismissAction())); + + // Mark that we're showing the info bar at this point. + _isShowingDocumentChangeErrorInfoBar = true; + } + + Action GetDefaultDismissAction(Action? additionalAction = null) + => () => + { + additionalAction?.Invoke(); + + // All info bar actions (except for 'show stack trace') dismiss the info bar, putting us back in the + // "we're not showing the user anything" state. + _isShowingDocumentChangeErrorInfoBar = false; + }; } protected override void ApplyDocumentInfoChanged(DocumentId documentId, DocumentInfo updatedInfo) diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 26238f9c4a5e1..74b583264d6cd 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1879,4 +1879,13 @@ Additional information: {1} Show completion list after a character is typed + + Error encountered updating '{0}' + + + Ignore + + + Ignore (including future errors) + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs b/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs index a7827976d7905..4b07d41a2a94a 100644 --- a/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs +++ b/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs @@ -17,10 +17,11 @@ internal readonly struct TelemetryFeatureName // Local services: - public static readonly TelemetryFeatureName CodeFixProvider = GetClientFeatureName("CodeFixProvider"); - public static readonly TelemetryFeatureName InlineRename = GetClientFeatureName("InlineRename"); + public static readonly TelemetryFeatureName CodeFixProvider = GetClientFeatureName(nameof(CodeFixProvider)); + public static readonly TelemetryFeatureName InlineRename = GetClientFeatureName(nameof(InlineRename)); public static readonly TelemetryFeatureName LegacySuppressionFix = GetClientFeatureName("TelemetryFeatureName"); - public static readonly TelemetryFeatureName VirtualMemoryNotification = GetClientFeatureName("VirtualMemoryNotification"); + public static readonly TelemetryFeatureName VirtualMemoryNotification = GetClientFeatureName(nameof(VirtualMemoryNotification)); + public static readonly TelemetryFeatureName Workspace = GetClientFeatureName(nameof(Workspace)); private readonly string _name; private readonly string _kind; From 5b1a64d8f744fb812e9320f963d3923a62c742ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 11:55:51 -0700 Subject: [PATCH 026/114] Simplify --- .../Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index cca7bb3d011ed..dc9080769580d 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1249,16 +1249,16 @@ void ReportErrorChangingDocumentText(Exception exception) TelemetryFeatureName.Workspace, exception, // 'Show stack trace' will not dismiss the info bar. - new InfoBarUI(WorkspacesResources.Show_Stack_Trace, InfoBarUI.UIKind.HyperLink, + new(WorkspacesResources.Show_Stack_Trace, InfoBarUI.UIKind.HyperLink, () => errorReportingService.ShowDetailedErrorInfo(exception), closeAfterAction: false), // 'Ignore' just closes the info bar, but allows future errors to show up. - new InfoBarUI(ServicesVSResources.Ignore, InfoBarUI.UIKind.Button, GetDefaultDismissAction()), + new(ServicesVSResources.Ignore, InfoBarUI.UIKind.Button, GetDefaultDismissAction()), // 'Ignore (including future errors) closes the info bar, but also sets the flag so the user gets no more messages // in the current session. - new InfoBarUI(ServicesVSResources.Ignore_including_future_errors, InfoBarUI.UIKind.Button, GetDefaultDismissAction( + new(ServicesVSResources.Ignore_including_future_errors, InfoBarUI.UIKind.Button, GetDefaultDismissAction( () => _ignoreDocumentTextChangeErrors = true)), // Close button is the same as 'ignore'. It closes the info bar, but allows future errors to show up. - new InfoBarUI(string.Empty, InfoBarUI.UIKind.Close, GetDefaultDismissAction())); + new(string.Empty, InfoBarUI.UIKind.Close, GetDefaultDismissAction())); // Mark that we're showing the info bar at this point. _isShowingDocumentChangeErrorInfoBar = true; From 8d1b410b5da7dab6e7460ea428deb7c740e6cbf9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 12:09:48 -0700 Subject: [PATCH 027/114] Add resource strings --- .../Core/Def/xlf/ServicesVSResources.cs.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.de.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.es.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.fr.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.it.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.ja.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.ko.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.pl.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.pt-BR.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.ru.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.tr.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.zh-Hans.xlf | 15 +++++++++++++++ .../Core/Def/xlf/ServicesVSResources.zh-Hant.xlf | 15 +++++++++++++++ 13 files changed, 195 insertions(+) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index c0cf5e57da1f1..93343aa29da77 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -412,6 +412,11 @@ Celé řešení a externí zdroje + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Chyba při aktualizaci potlačení: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Implementovaná rozhraní diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 6b22dba31cd9f..238ccbc890583 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -412,6 +412,11 @@ Gesamte Projektmappe und externe Quellen + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Fehler bei der Aktualisierung von Unterdrückungen: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Implementierte Schnittstellen diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 672a4438de516..e97aedc348e2d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -412,6 +412,11 @@ Solución completa y orígenes externos + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Actualización de errores de forma periódica: {0} @@ -517,6 +522,16 @@ Id. + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Interfaces implementadas diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index c595c0aca3fb7..832badf4373db 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -412,6 +412,11 @@ Solution entière et sources externes + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Erreur lors de la mise à jour des suppressions : {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Interfaces implémentées diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 6eb0a055e1c8e..49c8da4985737 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -412,6 +412,11 @@ Intera soluzione e origini esterne + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Errore durante l'aggiornamento delle eliminazioni: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Interfacce implementate diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 24cf1b73246b3..c730e80b8fdd1 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -412,6 +412,11 @@ ソリューションと外部ソース全体 + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} 抑制の更新でエラーが発生しました: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces 実装されたインターフェイス diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index efaea1089f353..a5bab91228241 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -412,6 +412,11 @@ 전체 솔루션 및 외부 원본 + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} 표시 중지를 업데이트하는 동안 오류가 발생했습니다. {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces 구현된 인터페이스 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 703fc107fed11..9dc33343730df 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -412,6 +412,11 @@ Całe rozwiązanie i źródła zewnętrzne + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Błąd podczas pomijania aktualizacji: {0} @@ -517,6 +522,16 @@ Identyfikator + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Wdrożone interfejsy diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 28994743218b1..8a10cc83cb598 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -412,6 +412,11 @@ Solução Inteira e Fontes Externas + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Erro ao atualizar supressões: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Interfaces implementadas diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index dea24751cc072..34e93760d7a17 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -412,6 +412,11 @@ Все решение и внешние источники + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Ошибка при обновлении подавлений: {0} @@ -517,6 +522,16 @@ ИД + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Реализованные интерфейсы diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index ef23090f2454a..8bf30ab2888be 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -412,6 +412,11 @@ Tam Çözüm ve Dış Kaynaklar + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} Gizlemeler güncellenirken hata oluştu: {0} @@ -517,6 +522,16 @@ Kimlik + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces Uygulanan arabirimler diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 2a6cebedfc557..7a70e723af51c 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -412,6 +412,11 @@ 整个解决方案和外部源 + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} 更新抑制时出现错误: {0} @@ -517,6 +522,16 @@ ID + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces 实现的接口 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index a4263d1a4562d..9ad256b581334 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -412,6 +412,11 @@ 整個解決方案與外部來源 + + Error encountered updating '{0}' + Error encountered updating '{0}' + + Error updating suppressions: {0} 更新歸併時發生錯誤: {0} @@ -517,6 +522,16 @@ 識別碼 + + Ignore + Ignore + + + + Ignore (including future errors) + Ignore (including future errors) + + Implemented interfaces 已實作的介面 From 318c5ffce5f53d0474d560374353270f826841ea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 12:10:55 -0700 Subject: [PATCH 028/114] Add return --- .../Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index dc9080769580d..1e1e81f0cb90d 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1232,6 +1232,7 @@ private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) catch (Exception ex) when (FatalError.ReportAndCatch(ex)) { ReportErrorChangingDocumentText(ex); + return; } void ReportErrorChangingDocumentText(Exception exception) From 41f593f88ce59fa36a0b0e47db201c1fde0f068b Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 23 Apr 2025 07:00:15 +1000 Subject: [PATCH 029/114] Update src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs Co-authored-by: Dustin Campbell --- .../Features/Cohost/IRazorCohostDynamicRegistrationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs index c21bc637df708..ea71386303872 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -[Obsolete("Use ICohostStartupService instead")] +[Obsolete($"Use {nameof(ICohostStartupService)} instead")] internal interface IRazorCohostDynamicRegistrationService { Task RegisterAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); From 04bac5b9abba29a5238da17f6488903d4485feea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 15:16:22 -0700 Subject: [PATCH 030/114] Reapply "Remove dependency on EditorFeatures from codelens layer (#78045)" (#78162) This reverts commit 89219d14b0e63b393632f62a69044ab6f0d7c345. --- ...soft.CodeAnalysis.LanguageServer.Protocol.csproj | 13 +++++++------ ...ft.VisualStudio.LanguageServices.CodeLens.csproj | 2 +- .../CodeLens/ReferenceCodeLensProvider.cs | 12 ++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index f34e515575b44..cd1328b0bab7f 100644 --- a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -71,17 +71,18 @@ - + - - - - - + + + + + + diff --git a/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj b/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj index 0f58e2bf74514..43bc7dc630c3d 100644 --- a/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj +++ b/src/VisualStudio/CodeLens/Microsoft.VisualStudio.LanguageServices.CodeLens.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs b/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs index c1fe6df4112ee..c05b0582d6acd 100644 --- a/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs +++ b/src/VisualStudio/CodeLens/ReferenceCodeLensProvider.cs @@ -12,8 +12,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeLens; using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.VisualStudio.Core.Imaging; using Microsoft.VisualStudio.Language.CodeLens; using Microsoft.VisualStudio.Language.CodeLens.Remoting; @@ -104,7 +104,7 @@ private async Task PollForUpdatesAsync() var projectVersions = await _lazyCodeLensCallbackService.Value.InvokeAsync>( this, - nameof(ICodeLensContext.GetProjectVersionsAsync), + "GetProjectVersionsAsync", [keys], _cancellationTokenSource.Token).ConfigureAwait(false); @@ -200,7 +200,7 @@ public void Dispose() // raw data from Roslyn OOP such as razor find all reference results var referenceCountOpt = await _callbackService.InvokeAsync( _owner, - nameof(ICodeLensContext.GetReferenceCountAsync), + "GetReferenceCountAsync", [Descriptor, descriptorContext, _calculatedReferenceCount], cancellationToken).ConfigureAwait(false); @@ -243,7 +243,7 @@ public async Task GetDetailsAsync(CodeLensDescriptorC // raw data from Roslyn OOP such as razor find all reference results var referenceLocationDescriptors = await _callbackService.InvokeAsync<(string projectVersion, ImmutableArray references)?>( _owner, - nameof(ICodeLensContext.FindReferenceLocationsAsync), + "FindReferenceLocationsAsync", [Descriptor, descriptorContext], cancellationToken).ConfigureAwait(false); @@ -263,8 +263,8 @@ public async Task GetDetailsAsync(CodeLensDescriptorC ImageId imageId = default; if (referenceLocationDescriptor.Glyph.HasValue) { - var moniker = referenceLocationDescriptor.Glyph.Value.GetImageMoniker(); - imageId = new ImageId(moniker.Guid, moniker.Id); + var imageData = referenceLocationDescriptor.Glyph.Value.GetVsImageData(); + imageId = new ImageId(imageData.guid, imageData.id); } return new CodeLensDetailEntryDescriptor() From 409b86cfb189371bace2125fd32555f0ec3b5fa8 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 22 Apr 2025 15:42:36 -0700 Subject: [PATCH 031/114] Cache MEF catalog in servicehub process (#78122) Cache the MEF catalog created in the roslyn code analysis process. On my dev machine, the creation of the MEF catalog in our CA process takes about 500 ms. With this change, runs that were able to load from the cache saw that time reduced to about 150 ms. The high-level change here is to piggy back on the work that Joey did a while back to add MEF catalog caching to our VSCode language server. This PR just moves that code down to a layer where our service hub code can reference, and generalized it a bit to fit into what the service hub code can provide. One slight additional change in this PR was to add a new remote service to handle passing over the proper location to store the mef cache. I didn't feel comfortable fitting this into the previous first message sent across (IRemoteProcessTelemetryService), so I created IRemoteInitializationService to better fit what I wanted. This led to creating a separate type to handle the export provider creation (RemoteExportProvider) instead of tying this up into RemoteWorkspaceManager. --- eng/targets/Services.props | 1 + .../Host/Mef/CodeStyleHostLanguageServices.cs | 2 +- .../ExportProviderBuilderTests.cs | 4 +- .../LanguageServerTestComposition.cs | 3 +- .../ExportProviderBuilder.cs | 229 ------------------ .../LanguageServerExportProviderBuilder.cs | 120 +++++++++ .../Program.cs | 4 +- .../Services/ExtensionAssemblyManager.cs | 1 + .../VisualStudioRemoteHostClientProvider.cs | 18 +- .../Services/ServiceHubServicesTests.cs | 14 +- .../IRemoteProcessTelemetryService.cs | 10 +- .../Workspace/Host/Mef/MefHostServices.cs | 6 +- .../SolutionWithSourceGeneratorTests.cs | 4 +- .../MEF/FeaturesTestCompositions.cs | 2 +- .../Remote/InProcRemostHostClient.cs | 1 + .../Remote/Core/ExportProviderBuilder.cs | 204 ++++++++++++++++ .../Core/IRemoteInitializationService.cs | 19 ++ .../Core/RemoteWorkspacesResources.resx | 3 + .../Remote/Core/ServiceDescriptors.cs | 1 + .../Remote/Core/ServiceHubRemoteHostClient.cs | 12 +- .../Core/xlf/RemoteWorkspacesResources.cs.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.de.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.es.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.fr.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.it.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.ja.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.ko.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.pl.xlf | 5 + .../xlf/RemoteWorkspacesResources.pt-BR.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.ru.xlf | 5 + .../Core/xlf/RemoteWorkspacesResources.tr.xlf | 5 + .../xlf/RemoteWorkspacesResources.zh-Hans.xlf | 5 + .../xlf/RemoteWorkspacesResources.zh-Hant.xlf | 5 + .../Host/RemoteExportProviderBuilder.cs | 107 ++++++++ .../ServiceHub/Host/RemoteWorkspaceManager.cs | 66 +---- .../Services/BrokeredServiceBase.cs | 4 +- .../RemoteInitializationService.cs | 39 +++ .../RemoteProcessTelemetryService.cs | 16 -- .../Core/Helpers/MefHostServicesHelpers.cs | 30 ++- 39 files changed, 640 insertions(+), 345 deletions(-) delete mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs create mode 100644 src/Workspaces/Remote/Core/ExportProviderBuilder.cs create mode 100644 src/Workspaces/Remote/Core/IRemoteInitializationService.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs create mode 100644 src/Workspaces/Remote/ServiceHub/Services/Initialization/RemoteInitializationService.cs diff --git a/eng/targets/Services.props b/eng/targets/Services.props index 409f1b282f9a1..c9b59f0c095bf 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -33,6 +33,7 @@ + diff --git a/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs b/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs index 38d1eefe4a63b..340b67afd3947 100644 --- a/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs +++ b/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs @@ -49,7 +49,7 @@ private static ImmutableArray CreateAssemblies(string languageName) } return MefHostServices.DefaultAssemblies.Concat( - MefHostServicesHelpers.LoadNearbyAssemblies(assemblyNames)); + MefHostServicesHelpers.LoadNearbyAssemblies(assemblyNames.ToImmutableAndClear())); } IEnumerable> IMefHostExportProvider.GetExports() diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs index 7148ec5e9a274..85d5d3d542de9 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs @@ -148,7 +148,7 @@ public ImportType(ExportedType t) { } private async Task AssertCacheWriteWasAttemptedAsync() { - var cacheWriteTask = ExportProviderBuilder.TestAccessor.GetCacheWriteTask(); + var cacheWriteTask = LanguageServerExportProviderBuilder.TestAccessor.GetCacheWriteTask(); Assert.NotNull(cacheWriteTask); await cacheWriteTask; @@ -156,7 +156,7 @@ private async Task AssertCacheWriteWasAttemptedAsync() private void AssertNoCacheWriteWasAttempted() { - var cacheWriteTask2 = ExportProviderBuilder.TestAccessor.GetCacheWriteTask(); + var cacheWriteTask2 = LanguageServerExportProviderBuilder.TestAccessor.GetCacheWriteTask(); Assert.Null(cacheWriteTask2); } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs index 9667713fc60bc..9215dfc08b82b 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs @@ -33,6 +33,7 @@ public static Task CreateExportProviderAsync( UseStdIo: false); var extensionManager = ExtensionAssemblyManager.Create(serverConfiguration, loggerFactory); assemblyLoader = new CustomExportAssemblyLoader(extensionManager, loggerFactory); - return ExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory); + + return LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory, CancellationToken.None); } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs deleted file mode 100644 index 6f8f7fbd154a7..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs +++ /dev/null @@ -1,229 +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.Collections.Immutable; -using System.IO.Hashing; -using System.Text; -using Microsoft.CodeAnalysis.LanguageServer.Logging; -using Microsoft.CodeAnalysis.LanguageServer.Services; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.Composition; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.LanguageServer; - -internal sealed class ExportProviderBuilder -{ - // For testing purposes, track the last cache write task. - private static Task? _cacheWriteTask; - - public static async Task CreateExportProviderAsync( - ExtensionAssemblyManager extensionManager, - IAssemblyLoader assemblyLoader, - string? devKitDependencyPath, - string cacheDirectory, - ILoggerFactory loggerFactory) - { - // Clear any previous cache write task, so that it is easy to discern whether - // a cache write was attempted. - _cacheWriteTask = null; - - var logger = loggerFactory.CreateLogger(); - var baseDirectory = AppContext.BaseDirectory; - - // Load any Roslyn assemblies from the extension directory - var assemblyPaths = Directory.EnumerateFiles(baseDirectory, "Microsoft.CodeAnalysis*.dll"); - assemblyPaths = assemblyPaths.Concat(Directory.EnumerateFiles(baseDirectory, "Microsoft.ServiceHub*.dll")); - - // DevKit assemblies are not shipped in the main language server folder - // and not included in ExtensionAssemblyPaths (they get loaded into the default ALC). - // So manually add them to the MEF catalog here. - if (devKitDependencyPath != null) - { - assemblyPaths = assemblyPaths.Concat(devKitDependencyPath); - } - - // Add the extension assemblies to the MEF catalog. - assemblyPaths = assemblyPaths.Concat(extensionManager.ExtensionAssemblyPaths); - - // Get the cached MEF composition or create a new one. - var exportProviderFactory = await GetCompositionConfigurationAsync([.. assemblyPaths], assemblyLoader, cacheDirectory, logger); - - // Create an export provider, which represents a unique container of values. - // You can create as many of these as you want, but typically an app needs just one. - var exportProvider = exportProviderFactory.CreateExportProvider(); - - // Immediately set the logger factory, so that way it'll be available for the rest of the composition - exportProvider.GetExportedValue().SetFactory(loggerFactory); - - // Also add the ExtensionAssemblyManager so it will be available for the rest of the composition. - exportProvider.GetExportedValue().SetMefExtensionAssemblyManager(extensionManager); - - return exportProvider; - } - - private static async Task GetCompositionConfigurationAsync( - ImmutableArray assemblyPaths, - IAssemblyLoader assemblyLoader, - string cacheDirectory, - ILogger logger) - { - // Create a MEF resolver that can resolve assemblies in the extension contexts. - var resolver = new Resolver(assemblyLoader); - - var compositionCacheFile = GetCompositionCacheFilePath(cacheDirectory, assemblyPaths); - - // Try to load a cached composition. - try - { - if (File.Exists(compositionCacheFile)) - { - logger.LogTrace($"Loading cached MEF catalog: {compositionCacheFile}"); - - CachedComposition cachedComposition = new(); - using FileStream cacheStream = new(compositionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true); - var exportProviderFactory = await cachedComposition.LoadExportProviderFactoryAsync(cacheStream, resolver); - - return exportProviderFactory; - } - } - catch (Exception ex) - { - // Log the error, and move on to recover by recreating the MEF composition. - logger.LogError($"Loading cached MEF composition failed: {ex}"); - } - - logger.LogTrace($"Composing MEF catalog using:{Environment.NewLine}{string.Join($" {Environment.NewLine}", assemblyPaths)}."); - - var discovery = PartDiscovery.Combine( - resolver, - new AttributedPartDiscovery(resolver, isNonPublicSupported: true), // "NuGet MEF" attributes (Microsoft.Composition) - new AttributedPartDiscoveryV1(resolver)); - - var catalog = ComposableCatalog.Create(resolver) - .AddParts(await discovery.CreatePartsAsync(assemblyPaths)) - .WithCompositionService(); // Makes an ICompositionService export available to MEF parts to import - - // Assemble the parts into a valid graph. - var config = CompositionConfiguration.Create(catalog); - - // Verify we only have expected errors. - ThrowOnUnexpectedErrors(config, catalog, logger); - - // Try to cache the composition. - _cacheWriteTask = WriteCompositionCacheAsync(compositionCacheFile, config, logger).ReportNonFatalErrorAsync(); - - // Prepare an ExportProvider factory based on this graph. - return config.CreateExportProviderFactory(); - } - - private static string GetCompositionCacheFilePath(string cacheDirectory, ImmutableArray assemblyPaths) - { - // This should vary based on .NET runtime major version so that as some of our processes switch between our target - // .NET version and the user's selected SDK runtime version (which may be newer), the MEF cache is kept isolated. - // This can be important when the MEF catalog records full assembly names such as "System.Runtime, 8.0.0.0" yet - // we might be running on .NET 7 or .NET 8, depending on the particular session and user settings. - var cacheSubdirectory = $".NET {Environment.Version.Major}"; - - return Path.Combine(cacheDirectory, cacheSubdirectory, $"c#-languageserver.{ComputeAssemblyHash(assemblyPaths)}.mef-composition"); - - static string ComputeAssemblyHash(ImmutableArray assemblyPaths) - { - // Ensure AssemblyPaths are always in the same order. - assemblyPaths = assemblyPaths.Sort(); - - var assemblies = new StringBuilder(); - foreach (var assemblyPath in assemblyPaths) - { - // Include assembly path in the hash so that changes to the set of included - // assemblies cause the composition to be rebuilt. - assemblies.Append(assemblyPath); - // Include the last write time in the hash so that newer assemblies written - // to the same location cause the composition to be rebuilt. - assemblies.Append(File.GetLastWriteTimeUtc(assemblyPath).ToString("F")); - } - - var hash = XxHash128.Hash(Encoding.UTF8.GetBytes(assemblies.ToString())); - // Convert to filename safe base64 string. - return Convert.ToBase64String(hash).Replace('+', '-').Replace('/', '_').TrimEnd('='); - } - } - - private static async Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, ILogger logger) - { - try - { - await Task.Yield(); - - if (Path.GetDirectoryName(compositionCacheFile) is string directory) - { - Directory.CreateDirectory(directory); - } - - CachedComposition cachedComposition = new(); - var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - using (FileStream cacheStream = new(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) - { - await cachedComposition.SaveAsync(config, cacheStream); - } - - File.Move(tempFilePath, compositionCacheFile, overwrite: true); - } - catch (Exception ex) - { - logger.LogError($"Failed to save MEF cache: {ex}"); - } - } - - private static void ThrowOnUnexpectedErrors(CompositionConfiguration configuration, ComposableCatalog catalog, ILogger logger) - { - // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior. - // Currently we are expecting the following: - // "----- CompositionError level 1 ------ - // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService.ctor(service): expected exactly 1 export matching constraints: - // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService - // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService - // but found 0. - // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService - // - // Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider.ctor(implementation): expected exactly 1 export matching constraints: - // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation - // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation - // but found 0. - // part definition Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider - // - // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor.ctor(workspaceProvider): expected exactly 1 export matching constraints: - // Contract name: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider - // TypeIdentityName: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider - // but found 0. - // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor - - var erroredParts = configuration.CompositionErrors.FirstOrDefault()?.SelectMany(error => error.Parts).Select(part => part.Definition.Type.Name) ?? []; - var expectedErroredParts = new string[] { "CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor" }; - var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErroredParts.Contains(part)); - - if (hasUnexpectedErroredParts || !catalog.DiscoveredParts.DiscoveryErrors.IsEmpty) - { - try - { - catalog.DiscoveredParts.ThrowOnErrors(); - configuration.ThrowOnErrors(); - } - catch (CompositionFailedException ex) - { - // The ToString for the composition failed exception doesn't output a nice set of errors by default, so log it separately - logger.LogError($"Encountered errors in the MEF composition:{Environment.NewLine}{ex.ErrorsAsString}"); - throw; - } - } - } - - internal static class TestAccessor - { -#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods - public static Task? GetCacheWriteTask() => _cacheWriteTask; -#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods - } -} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs new file mode 100644 index 0000000000000..82eb3cf9d89d7 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.LanguageServer.Logging; +using Microsoft.CodeAnalysis.LanguageServer.Services; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.LanguageServer; + +internal sealed class LanguageServerExportProviderBuilder : ExportProviderBuilder +{ + private readonly ILogger _logger; + + // For testing purposes, track the last cache write task. + private static Task? s_cacheWriteTask_forTestingPurposesOnly; + + private LanguageServerExportProviderBuilder( + ImmutableArray assemblyPaths, + Resolver resolver, + string cacheDirectory, + string catalogPrefix, + ILoggerFactory loggerFactory) + : base(assemblyPaths, resolver, cacheDirectory, catalogPrefix) + { + _logger = loggerFactory.CreateLogger(); + } + + public static async Task CreateExportProviderAsync( + ExtensionAssemblyManager extensionManager, + IAssemblyLoader assemblyLoader, + string? devKitDependencyPath, + string cacheDirectory, + ILoggerFactory loggerFactory, + CancellationToken cancellationToken) + { + var baseDirectory = AppContext.BaseDirectory; + + // Load any Roslyn assemblies from the extension directory + using var _ = ArrayBuilder.GetInstance(out var assemblyPathsBuilder); + + // Don't catch IO exceptions as it's better to fail to build the catalog than give back + // a partial catalog that will surely blow up later. + assemblyPathsBuilder.AddRange(Directory.EnumerateFiles(baseDirectory, "Microsoft.CodeAnalysis*.dll")); + assemblyPathsBuilder.AddRange(Directory.EnumerateFiles(baseDirectory, "Microsoft.ServiceHub*.dll")); + + // DevKit assemblies are not shipped in the main language server folder + // and not included in ExtensionAssemblyPaths (they get loaded into the default ALC). + // So manually add them to the MEF catalog here. + if (devKitDependencyPath != null) + assemblyPathsBuilder.Add(devKitDependencyPath); + + // Add the extension assemblies to the MEF catalog. + assemblyPathsBuilder.AddRange(extensionManager.ExtensionAssemblyPaths); + + // Create a MEF resolver that can resolve assemblies in the extension contexts. + var builder = new LanguageServerExportProviderBuilder( + assemblyPathsBuilder.ToImmutableAndClear(), + new Resolver(assemblyLoader), + cacheDirectory, + catalogPrefix: "c#-languageserver", + loggerFactory); + var exportProvider = await builder.CreateExportProviderAsync(cancellationToken); + + // Also add the ExtensionAssemblyManager so it will be available for the rest of the composition. + exportProvider.GetExportedValue().SetMefExtensionAssemblyManager(extensionManager); + + // Immediately set the logger factory, so that way it'll be available for the rest of the composition + exportProvider.GetExportedValue().SetFactory(loggerFactory); + + return exportProvider; + } + + protected override void LogError(string message) + => _logger.LogError(message); + + protected override void LogTrace(string message) + => _logger.LogTrace(message); + + protected override Task CreateExportProviderAsync(CancellationToken cancellationToken) + { + // Clear any previous cache write task, so that it is easy to discern whether + // a cache write was attempted. + s_cacheWriteTask_forTestingPurposesOnly = null; + + return base.CreateExportProviderAsync(cancellationToken); + } + + protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions) + { + // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior. + var expectedErrorPartsSet = new HashSet(["CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor"]); + var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part)); + + return hasUnexpectedErroredParts || !partDiscoveryExceptions.IsEmpty; + } + + protected override Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, CancellationToken cancellationToken) + { + s_cacheWriteTask_forTestingPurposesOnly = base.WriteCompositionCacheAsync(compositionCacheFile, config, cancellationToken); + + return s_cacheWriteTask_forTestingPurposesOnly; + } + + protected override void PerformCacheDirectoryCleanup(DirectoryInfo directoryInfo, CancellationToken cancellationToken) + { + // No cache directory cleanup is needed for the language server. + } + + internal static class TestAccessor + { +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + public static Task? GetCacheWriteTask() => s_cacheWriteTask_forTestingPurposesOnly; +#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index 56c2d092bf6fe..779bb65eea742 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -2,7 +2,6 @@ // 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.Immutable; using System.CommandLine; using System.Diagnostics; using System.IO.Pipes; @@ -19,7 +18,6 @@ using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; -using Roslyn.Utilities; using RoslynLog = Microsoft.CodeAnalysis.Internal.Log; // Setting the title can fail if the process is run without a window, such @@ -98,7 +96,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation var cacheDirectory = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location)!, "cache"); - using var exportProvider = await ExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory); + using var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory, cancellationToken); // LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to // 'automatic' for now. diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs index d1ca799deec39..7c4b98c63a987 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs @@ -7,6 +7,7 @@ using System.Runtime.Loader; using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; using Microsoft.Extensions.Logging; namespace Microsoft.CodeAnalysis.LanguageServer.Services; diff --git a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs index 1f133e65019a9..76e12659cbadb 100644 --- a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs +++ b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs @@ -17,7 +17,10 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.ServiceBroker; +using Microsoft.VisualStudio.Shell.Settings; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using VSThreading = Microsoft.VisualStudio.Threading; @@ -31,6 +34,7 @@ internal sealed class Factory : IWorkspaceServiceFactory { private readonly VisualStudioWorkspace _vsWorkspace; private readonly IVsService _brokeredServiceContainer; + private readonly IServiceProvider _serviceProvider; private readonly AsynchronousOperationListenerProvider _listenerProvider; private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers; private readonly IGlobalOptionService _globalOptions; @@ -44,6 +48,7 @@ internal sealed class Factory : IWorkspaceServiceFactory public Factory( VisualStudioWorkspace vsWorkspace, IVsService brokeredServiceContainer, + SVsServiceProvider serviceProvider, AsynchronousOperationListenerProvider listenerProvider, IGlobalOptionService globalOptions, IThreadingContext threadingContext, @@ -52,6 +57,7 @@ public Factory( _globalOptions = globalOptions; _vsWorkspace = vsWorkspace; _brokeredServiceContainer = brokeredServiceContainer; + _serviceProvider = serviceProvider; _listenerProvider = listenerProvider; _threadingContext = threadingContext; _callbackDispatchers = new RemoteServiceCallbackDispatcherRegistry(callbackDispatchers); @@ -79,7 +85,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) // If we have a cached vs instance, then we can return that instance since we know they have the same host services. // Otherwise, create and cache an instance based on vs workspace for future callers with same services. if (_cachedVSInstance is null) - _cachedVSInstance = new VisualStudioRemoteHostClientProvider(_vsWorkspace.Services.SolutionServices, _globalOptions, _brokeredServiceContainer, _threadingContext, _listenerProvider, _callbackDispatchers); + _cachedVSInstance = new VisualStudioRemoteHostClientProvider(_vsWorkspace.Services.SolutionServices, _globalOptions, _brokeredServiceContainer, _serviceProvider, _threadingContext, _listenerProvider, _callbackDispatchers); return _cachedVSInstance; } @@ -90,14 +96,17 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly IGlobalOptionService _globalOptions; private readonly VSThreading.AsyncLazy _lazyClient; private readonly IVsService _brokeredServiceContainer; + private readonly IServiceProvider _serviceProvider; private readonly AsynchronousOperationListenerProvider _listenerProvider; private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers; private readonly TaskCompletionSource _clientCreationSource = new(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly IThreadingContext _threadingContext; private VisualStudioRemoteHostClientProvider( SolutionServices services, IGlobalOptionService globalOptions, IVsService brokeredServiceContainer, + IServiceProvider serviceProvider, IThreadingContext threadingContext, AsynchronousOperationListenerProvider listenerProvider, RemoteServiceCallbackDispatcherRegistry callbackDispatchers) @@ -105,12 +114,14 @@ private VisualStudioRemoteHostClientProvider( Services = services; _globalOptions = globalOptions; _brokeredServiceContainer = brokeredServiceContainer; + _serviceProvider = serviceProvider; _listenerProvider = listenerProvider; _callbackDispatchers = callbackDispatchers; + _threadingContext = threadingContext; // using VS AsyncLazy here since Roslyn's is not compatible with JTF. // Our ServiceBroker services may be invoked by other VS components under JTF. - _lazyClient = new VSThreading.AsyncLazy(CreateHostClientAsync, threadingContext.JoinableTaskFactory); + _lazyClient = new VSThreading.AsyncLazy(CreateHostClientAsync, _threadingContext.JoinableTaskFactory); } private async Task CreateHostClientAsync() @@ -122,9 +133,10 @@ private VisualStudioRemoteHostClientProvider( var configuration = _globalOptions.GetOption(RemoteHostOptionsStorage.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0; + var localSettingsDirectory = new ShellSettingsManager(_serviceProvider).GetApplicationDataFolder(ApplicationDataFolder.LocalSettings); // VS AsyncLazy does not currently support cancellation: - var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false); + var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, localSettingsDirectory, _listenerProvider, serviceBroker, _callbackDispatchers, _threadingContext.DisposalToken).ConfigureAwait(false); // proffer in-proc brokered services: _ = brokeredServiceContainer.Proffer(SolutionAssetProvider.ServiceDescriptor, (_, _, _, _) => ValueTaskFactory.FromResult(new SolutionAssetProvider(Services))); diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index e120228f1ce92..bd7e8d7d6ff42 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -738,8 +738,8 @@ internal async Task TestSourceGenerationExecution_RegenerateOnEdit( var workspaceConfigurationService = workspace.Services.GetRequiredService(); - var remoteProcessId = await client.TryInvokeAsync( - (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + _ = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, TempRoot.Root, cancellationToken), CancellationToken.None).ConfigureAwait(false); var solution = workspace.CurrentSolution; @@ -822,8 +822,8 @@ internal async Task TestSourceGenerationExecution_MinorVersionChange_NoActualCha var workspaceConfigurationService = workspace.Services.GetRequiredService(); - var remoteProcessId = await client.TryInvokeAsync( - (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + _ = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, TempRoot.Root, cancellationToken), CancellationToken.None).ConfigureAwait(false); var solution = workspace.CurrentSolution; @@ -877,8 +877,8 @@ internal async Task TestSourceGenerationExecution_MajorVersionChange_NoActualCha var workspaceConfigurationService = workspace.Services.GetRequiredService(); - var remoteProcessId = await client.TryInvokeAsync( - (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + _ = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, TempRoot.Root, cancellationToken), CancellationToken.None).ConfigureAwait(false); var solution = workspace.CurrentSolution; @@ -1686,7 +1686,7 @@ private static Solution Populate(Solution solution) ], [ "vb additional file content" - ], [solution.ProjectIds.First()]); + ], [solution.ProjectIds[0]]); solution = AddProject(solution, LanguageNames.CSharp, [ diff --git a/src/Workspaces/Core/Portable/Telemetry/IRemoteProcessTelemetryService.cs b/src/Workspaces/Core/Portable/Telemetry/IRemoteProcessTelemetryService.cs index 96df52c92932f..33d7cbf0a34cc 100644 --- a/src/Workspaces/Core/Portable/Telemetry/IRemoteProcessTelemetryService.cs +++ b/src/Workspaces/Core/Portable/Telemetry/IRemoteProcessTelemetryService.cs @@ -2,10 +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.Immutable; using System.Threading; using System.Threading.Tasks; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; namespace Microsoft.CodeAnalysis.Remote; @@ -21,11 +20,4 @@ internal interface IRemoteProcessTelemetryService /// Initializes telemetry session. /// ValueTask InitializeTelemetrySessionAsync(int hostProcessId, string serializedSession, bool logDelta, CancellationToken cancellationToken); - - /// - /// Sets for the process. - /// Called as soon as the remote process is created but can't guarantee that solution entities (projects, documents, syntax trees) have not been created beforehand. - /// - /// Process ID of the remote process. - ValueTask InitializeAsync(WorkspaceConfigurationOptions options, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs b/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs index 38a129b38e69b..f5283e66ea43a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/Mef/MefHostServices.cs @@ -104,7 +104,7 @@ public static ImmutableArray DefaultAssemblies // Used to build a MEF composition using the main workspaces assemblies and the known VisualBasic/CSharp workspace assemblies. // updated: includes feature assemblies since they now have public API's. - private static readonly string[] s_defaultAssemblyNames = + internal static ImmutableArray DefaultAssemblyNames = [ "Microsoft.CodeAnalysis.Workspaces", "Microsoft.CodeAnalysis.CSharp.Workspaces", @@ -117,11 +117,11 @@ public static ImmutableArray DefaultAssemblies internal static bool IsDefaultAssembly(Assembly assembly) { var name = assembly.GetName().Name; - return s_defaultAssemblyNames.Contains(name); + return DefaultAssemblyNames.Contains(name); } private static ImmutableArray LoadDefaultAssemblies() - => MefHostServicesHelpers.LoadNearbyAssemblies(s_defaultAssemblyNames); + => MefHostServicesHelpers.LoadNearbyAssemblies(DefaultAssemblyNames); #endregion diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 095adae521835..ed6cc9b72ee9d 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -1387,8 +1387,8 @@ internal async Task UpdatingAnalyzerReferenceReloadsGenerators( var workspaceConfigurationService = workspace.Services.GetRequiredService(); - var remoteProcessId = await client.TryInvokeAsync( - (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options with { SourceGeneratorExecution = executionPreference }, cancellationToken), + _ = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options with { SourceGeneratorExecution = executionPreference }, TempRoot.Root, cancellationToken), CancellationToken.None).ConfigureAwait(false); var solution = workspace.CurrentSolution; diff --git a/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs b/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs index e9ae4216a379e..a929d0b77a49a 100644 --- a/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs +++ b/src/Workspaces/CoreTestUtilities/MEF/FeaturesTestCompositions.cs @@ -21,7 +21,7 @@ public static class FeaturesTestCompositions // We need to update tests to handle the options correctly before enabling the default provider. public static readonly TestComposition RemoteHost = TestComposition.Empty - .AddAssemblies(RemoteWorkspaceManager.RemoteHostAssemblies) + .AddAssemblies(MefHostServicesHelpers.LoadNearbyAssemblies(RemoteExportProviderBuilder.RemoteHostAssemblyNames)) .AddParts(typeof(TestSerializerService.Factory)); public static TestComposition WithTestHostParts(this TestComposition composition, TestHost host) diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 6800cd6da60b2..60abbcc98f941 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -193,6 +193,7 @@ public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? t RegisterRemoteBrokeredService(new RemoteFindUsagesService.Factory()); RegisterRemoteBrokeredService(new RemoteFullyQualifyService.Factory()); RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); + RegisterRemoteBrokeredService(new RemoteInitializationService.Factory()); RegisterRemoteBrokeredService(new RemoteKeepAliveService.Factory()); RegisterRemoteBrokeredService(new RemoteLegacySolutionEventsAggregationService.Factory()); RegisterRemoteBrokeredService(new RemoteMissingImportDiscoveryService.Factory()); diff --git a/src/Workspaces/Remote/Core/ExportProviderBuilder.cs b/src/Workspaces/Remote/Core/ExportProviderBuilder.cs new file mode 100644 index 0000000000000..2eb905ffb7314 --- /dev/null +++ b/src/Workspaces/Remote/Core/ExportProviderBuilder.cs @@ -0,0 +1,204 @@ +// 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.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; +using Microsoft.VisualStudio.Composition; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +internal abstract class ExportProviderBuilder( + ImmutableArray assemblyPaths, + Resolver resolver, + string cacheDirectory, + string catalogPrefix) +{ + private const string CatalogSuffix = ".mef-composition"; + + protected ImmutableArray AssemblyPaths { get; } = assemblyPaths; + protected Resolver Resolver { get; } = resolver; + protected string CacheDirectory { get; } = cacheDirectory; + protected string CatalogPrefix { get; } = catalogPrefix; + + protected abstract void LogError(string message); + protected abstract void LogTrace(string message); + + protected virtual async Task CreateExportProviderAsync(CancellationToken cancellationToken) + { + // Get the cached MEF composition or create a new one. + var exportProviderFactory = await GetCompositionConfigurationAsync(cancellationToken).ConfigureAwait(false); + + // Create an export provider, which represents a unique container of values. + // You can create as many of these as you want, but typically an app needs just one. + var exportProvider = exportProviderFactory.CreateExportProvider(); + + return exportProvider; + } + + private async Task GetCompositionConfigurationAsync(CancellationToken cancellationToken) + { + // Determine the path to the MEF composition cache file for the given assembly paths. + var compositionCacheFile = GetCompositionCacheFilePath(); + + // Try to load a cached composition. + try + { + if (File.Exists(compositionCacheFile)) + { + LogTrace($"Loading cached MEF catalog: {compositionCacheFile}"); + + CachedComposition cachedComposition = new(); + using FileStream cacheStream = new(compositionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true); + var exportProviderFactory = await cachedComposition.LoadExportProviderFactoryAsync(cacheStream, Resolver, cancellationToken).ConfigureAwait(false); + + return exportProviderFactory; + } + } + catch (Exception ex) + { + // Log the error, and move on to recover by recreating the MEF composition. + LogError($"Loading cached MEF composition failed: {ex}"); + } + + LogTrace($"Composing MEF catalog using:{Environment.NewLine}{string.Join($" {Environment.NewLine}", AssemblyPaths)}."); + + var discovery = PartDiscovery.Combine( + Resolver, + new AttributedPartDiscovery(Resolver, isNonPublicSupported: true), // "NuGet MEF" attributes (Microsoft.Composition) + new AttributedPartDiscoveryV1(Resolver)); + + var parts = await discovery.CreatePartsAsync(AssemblyPaths, progress: null, cancellationToken).ConfigureAwait(false); + var catalog = ComposableCatalog.Create(Resolver) + .AddParts(parts) + .WithCompositionService(); // Makes an ICompositionService export available to MEF parts to import + + // Assemble the parts into a valid graph. + var config = CompositionConfiguration.Create(catalog); + + // Verify we only have expected errors. + + ThrowOnUnexpectedErrors(config, catalog); + + // Try to cache the composition. + _ = WriteCompositionCacheAsync(compositionCacheFile, config, cancellationToken).ReportNonFatalErrorAsync(); + + // Prepare an ExportProvider factory based on this graph. + return config.CreateExportProviderFactory(); + } + + /// + /// Returns the path to the MEF composition cache file. Inputs used to determine the file name include: + /// 1) The given assembly paths + /// 2) The last write times of the given assembly paths + /// 3) The .NET runtime major version + /// + private string GetCompositionCacheFilePath() + { + return Path.Combine(CacheDirectory, $"{CatalogPrefix}.{ComputeAssemblyHash(AssemblyPaths)}{CatalogSuffix}"); + + static string ComputeAssemblyHash(ImmutableArray assemblyPaths) + { + // Ensure AssemblyPaths are always in the same order. + assemblyPaths = assemblyPaths.Sort(StringComparer.Ordinal); + + var hashContents = new StringBuilder(); + + // This should vary based on .NET runtime major version so that as some of our processes switch between our target + // .NET version and the user's selected SDK runtime version (which may be newer), the MEF cache is kept isolated. + // This can be important when the MEF catalog records full assembly names such as "System.Runtime, 8.0.0.0" yet + // we might be running on .NET 7 or .NET 8, depending on the particular session and user settings. + hashContents.Append(Environment.Version.Major); + + foreach (var assemblyPath in assemblyPaths) + { + // Include assembly path in the hash so that changes to the set of included + // assemblies cause the composition to be rebuilt. + hashContents.Append(assemblyPath); + // Include the last write time in the hash so that newer assemblies written + // to the same location cause the composition to be rebuilt. + hashContents.Append(File.GetLastWriteTimeUtc(assemblyPath).ToString("F")); + } + + // Create base64 string of the hash. + var hashAsBase64String = Checksum.Create(hashContents.ToString()).ToString(); + + // Convert to filename safe base64 string. + return hashAsBase64String.Replace('+', '-').Replace('/', '_').TrimEnd('='); + } + } + + protected virtual async Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, CancellationToken cancellationToken) + { + // Generally, it's not a hard failure if this code doesn't execute to completion or even fails. The end effect would simply + // either be a non-existent or invalid file cached to disk. In the case of the file not getting cached, the next VS session + // will just detect the file doesn't exist and attempt to recreate the cache. In the case where the cached file contents are + // invalid, the next VS session will throw when attempting to read in the cached contents, and again, just recreate the cache. + try + { + await Task.Yield().ConfigureAwait(false); + + var directory = Path.GetDirectoryName(compositionCacheFile)!; + var directoryInfo = Directory.CreateDirectory(directory); + PerformCacheDirectoryCleanup(directoryInfo, cancellationToken); + + CachedComposition cachedComposition = new(); + var tempFilePath = Path.Combine(directory, Path.GetRandomFileName()); + using (FileStream cacheStream = new(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) + { + await cachedComposition.SaveAsync(config, cacheStream, cancellationToken).ConfigureAwait(false); + } + + File.Move(tempFilePath, compositionCacheFile); + } + catch (Exception ex) + { + LogError($"Failed to save MEF cache: {ex}"); + } + } + + protected virtual void PerformCacheDirectoryCleanup(DirectoryInfo directoryInfo, CancellationToken cancellationToken) + { + // Delete any existing cached files. + foreach (var fileInfo in directoryInfo.EnumerateFiles()) + { + // Failing to delete any file is fine, we'll just try again the next VS session in which we attempt + // to write a new cache + IOUtilities.PerformIO(fileInfo.Delete); + cancellationToken.ThrowIfCancellationRequested(); + } + } + + protected abstract bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions); + + private void ThrowOnUnexpectedErrors(CompositionConfiguration configuration, ComposableCatalog catalog) + { + // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior. + var erroredParts = configuration.CompositionErrors.FirstOrDefault()?.SelectMany(error => error.Parts).Select(part => part.Definition.Type.Name) ?? []; + + if (ContainsUnexpectedErrors(erroredParts, catalog.DiscoveredParts.DiscoveryErrors)) + { + try + { + catalog.DiscoveredParts.ThrowOnErrors(); + configuration.ThrowOnErrors(); + } + catch (CompositionFailedException ex) + { + // The ToString for the composition failed exception doesn't output a nice set of errors by default, so log it separately + LogError($"Encountered errors in the MEF composition:{Environment.NewLine}{ex.ErrorsAsString}"); + throw; + } + } + } +} diff --git a/src/Workspaces/Remote/Core/IRemoteInitializationService.cs b/src/Workspaces/Remote/Core/IRemoteInitializationService.cs new file mode 100644 index 0000000000000..547e09a8262f0 --- /dev/null +++ b/src/Workspaces/Remote/Core/IRemoteInitializationService.cs @@ -0,0 +1,19 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Remote; + +internal interface IRemoteInitializationService +{ + /// + /// Initializes values including for the process. + /// Called as soon as the remote process is created. + /// + /// Process ID of the remote process and an error message if the server encountered initialization issues. + ValueTask<(int processId, string? errorMessage)> InitializeAsync(WorkspaceConfigurationOptions options, string localSettingsDirectory, CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index d7a0780f01053..4aef0ad539f80 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -237,4 +237,7 @@ Extension message handler + + Initialization + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index 376fe434d2b9f..b2e113683a662 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -75,6 +75,7 @@ internal sealed class ServiceDescriptors (typeof(IRemoteNavigateToSearchService), typeof(IRemoteNavigateToSearchService.ICallback)), (typeof(IRemoteNavigationBarItemService), null), (typeof(IRemoteProcessTelemetryService), null), + (typeof(IRemoteInitializationService), null), (typeof(IRemoteRelatedDocumentsService), typeof(IRemoteRelatedDocumentsService.ICallback)), (typeof(IRemoteRenamerService), null), (typeof(IRemoteSemanticClassificationService), null), diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index 18f00af2c8e7a..085b92694ef20 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -54,6 +54,7 @@ private ServiceHubRemoteHostClient( public static async Task CreateAsync( SolutionServices services, RemoteProcessConfiguration configuration, + string localSettingsDirectory, AsynchronousOperationListenerProvider listenerProvider, IServiceBroker serviceBroker, RemoteServiceCallbackDispatcherRegistry callbackDispatchers, @@ -72,15 +73,18 @@ public static async Task CreateAsync( var workspaceConfigurationService = services.GetRequiredService(); - var remoteProcessId = await client.TryInvokeAsync( - (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, cancellationToken), + var remoteProcessIdAndErrorMessage = await client.TryInvokeAsync( + (service, cancellationToken) => service.InitializeAsync(workspaceConfigurationService.Options, localSettingsDirectory, cancellationToken), cancellationToken).ConfigureAwait(false); - if (remoteProcessId.HasValue) + if (remoteProcessIdAndErrorMessage.HasValue) { + if (remoteProcessIdAndErrorMessage.Value.errorMessage != null) + hubClient.Logger.TraceEvent(TraceEventType.Error, 1, $"ServiceHub initialization error: {remoteProcessIdAndErrorMessage.Value.errorMessage}"); + try { - client._remoteProcess = Process.GetProcessById(remoteProcessId.Value); + client._remoteProcess = Process.GetProcessById(remoteProcessIdAndErrorMessage.Value.processId); } catch (Exception e) { diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index c13b28a824e53..e0cf716580e3b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -87,6 +87,11 @@ Míra dědičnosti + + Initialization + Initialization + + Keep alive service Služba responzivity diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index 7d6b8a5105019..c61e7c1749e13 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -87,6 +87,11 @@ Vererbungsrand + + Initialization + Initialization + + Keep alive service Keepalive-Dienst diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 2d89bd4c37442..a5cc2d538350a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -87,6 +87,11 @@ Margen de herencia + + Initialization + Initialization + + Keep alive service Mantener el servicio activo diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 8909739999915..f989563604567 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -87,6 +87,11 @@ Marge d’héritage + + Initialization + Initialization + + Keep alive service Maintenir le service actif diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index 66074ff323f59..149c0bb4324a9 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -87,6 +87,11 @@ Margine di ereditarietà + + Initialization + Initialization + + Keep alive service Servizio keep-alive diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index 9bd73d55d6e0a..282462e1a440c 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -87,6 +87,11 @@ 継承の余白 + + Initialization + Initialization + + Keep alive service キープ アライブ サービス diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index df2ec3c901330..85c6ac95dfffc 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -87,6 +87,11 @@ 상속 여백 + + Initialization + Initialization + + Keep alive service 서비스 연결 유지 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index d034f1c2c3b99..3e34ecd0bb45b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -87,6 +87,11 @@ Margines dziedziczenia + + Initialization + Initialization + + Keep alive service Utrzymywanie aktywności usługi diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index 0371b6a72d63e..d13db956d4483 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -87,6 +87,11 @@ Margem de herança + + Initialization + Initialization + + Keep alive service Serviço Keep alive diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index 5fe0ce251989c..7ca04d5b162a3 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -87,6 +87,11 @@ Граница наследования + + Initialization + Initialization + + Keep alive service Служба проверки активности diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index c162e57f0d35e..c9089a3fa2f88 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -87,6 +87,11 @@ Devralma boşluğu + + Initialization + Initialization + + Keep alive service Etkin tutma hizmeti diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index 9d1e91d5f58aa..290651aa5bd14 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -87,6 +87,11 @@ 继承边距 + + Initialization + Initialization + + Keep alive service 保持活动状态的服务 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index b155db43b50a5..ead886b90f311 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -87,6 +87,11 @@ 繼承邊界 + + Initialization + Initialization + + Keep alive service 保持運作服務 diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs new file mode 100644 index 0000000000000..c5b11d31b372b --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteExportProviderBuilder.cs @@ -0,0 +1,107 @@ +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Composition; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed class RemoteExportProviderBuilder : ExportProviderBuilder +{ + internal static readonly ImmutableArray RemoteHostAssemblyNames = + MefHostServices.DefaultAssemblyNames + .Add("Microsoft.CodeAnalysis.ExternalAccess.AspNetCore") + .Add("Microsoft.CodeAnalysis.Remote.ServiceHub") + .Add("Microsoft.CodeAnalysis.ExternalAccess.Razor") + .Add("Microsoft.CodeAnalysis.Remote.Workspaces") + .Add("Microsoft.CodeAnalysis.ExternalAccess.Extensions"); + + private static ExportProvider? s_instance; + internal static ExportProvider ExportProvider + => s_instance ?? throw new InvalidOperationException($"Default export provider not initialized. Call {nameof(InitializeAsync)} first."); + + private StringBuilder? _errorMessages; + + private RemoteExportProviderBuilder( + ImmutableArray assemblyPaths, + Resolver resolver, + string cacheDirectory, + string catalogPrefix) + : base(assemblyPaths, resolver, cacheDirectory, catalogPrefix) + { + } + + public static async Task InitializeAsync(string localSettingsDirectory, CancellationToken cancellationToken) + { + var assemblyPaths = RemoteHostAssemblyNames + .Select(static assemblyName => MefHostServicesHelpers.TryFindNearbyAssemblyLocation(assemblyName)) + .WhereNotNull() + .AsImmutable(); + + var builder = new RemoteExportProviderBuilder( + assemblyPaths: assemblyPaths, + resolver: new Resolver(SimpleAssemblyLoader.Instance), + cacheDirectory: Path.Combine(localSettingsDirectory, "Roslyn", "RemoteHost", "Cache"), + catalogPrefix: "RoslynRemoteHost"); + + s_instance = await builder.CreateExportProviderAsync(cancellationToken).ConfigureAwait(false); + + return builder._errorMessages?.ToString(); + } + + protected override void LogError(string message) + { + _errorMessages ??= new StringBuilder(); + _errorMessages.AppendLine(message); + } + + protected override void LogTrace(string message) + { + } + + protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions) + { + // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior. + var expectedErrorPartsSet = new HashSet(["PythiaSignatureHelpProvider", "VSTypeScriptAnalyzerService", "CodeFixService"]); + var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part)); + + if (hasUnexpectedErroredParts) + return true; + + return partDiscoveryExceptions.Count > 0; + } + + private sealed class SimpleAssemblyLoader : IAssemblyLoader + { + public static readonly IAssemblyLoader Instance = new SimpleAssemblyLoader(); + + public Assembly LoadAssembly(AssemblyName assemblyName) + => Assembly.Load(assemblyName); + + public Assembly LoadAssembly(string assemblyFullName, string? codeBasePath) + { + var assemblyName = new AssemblyName(assemblyFullName); + if (!string.IsNullOrEmpty(codeBasePath)) + { + // Set the codebase path, if known, as a hint for the assembly loader. +#pragma warning disable SYSLIB0044 // https://github.com/dotnet/roslyn/issues/71510 + assemblyName.CodeBase = codeBasePath; +#pragma warning restore SYSLIB0044 + } + + return LoadAssembly(assemblyName); + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs index 274e260b057f2..29b8ed058f4cc 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs @@ -3,16 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; -using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Extensions; -using Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.Internal.EmbeddedLanguages; -using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.ServiceHub.Framework; -using Microsoft.VisualStudio.Composition; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -23,14 +17,6 @@ namespace Microsoft.CodeAnalysis.Remote; /// internal class RemoteWorkspaceManager { - internal static readonly ImmutableArray RemoteHostAssemblies = - MefHostServices.DefaultAssemblies - .Add(typeof(AspNetCoreEmbeddedLanguageClassifier).Assembly) - .Add(typeof(BrokeredServiceBase).Assembly) - .Add(typeof(IRazorLanguageServerTarget).Assembly) - .Add(typeof(RemoteWorkspacesResources).Assembly) - .Add(typeof(IExtensionWorkspaceMessageHandler<,>).Assembly); - /// /// Default workspace manager used by the product. Tests may specify a custom in order to override workspace services. @@ -59,8 +45,15 @@ internal class RemoteWorkspaceManager /// /// /// - internal static readonly RemoteWorkspaceManager Default = new( - workspace => new SolutionAssetCache(workspace, cleanupInterval: TimeSpan.FromSeconds(30), purgeAfter: TimeSpan.FromMinutes(1))); + private static readonly Lazy s_default = new(static () => + { + return new RemoteWorkspaceManager(CreateAssetCache); + + static SolutionAssetCache CreateAssetCache(RemoteWorkspace workspace) + => new SolutionAssetCache(workspace, cleanupInterval: TimeSpan.FromSeconds(30), purgeAfter: TimeSpan.FromMinutes(1)); + }); + + internal static RemoteWorkspaceManager Default => s_default.Value; private readonly RemoteWorkspace _workspace; internal readonly SolutionAssetCache SolutionAssetCache; @@ -78,27 +71,9 @@ public RemoteWorkspaceManager( SolutionAssetCache = createAssetCache(workspace); } - private static ComposableCatalog CreateCatalog(ImmutableArray assemblies) - { - var resolver = new Resolver(SimpleAssemblyLoader.Instance); - var discovery = new AttributedPartDiscovery(resolver, isNonPublicSupported: true); - var parts = Task.Run(async () => await discovery.CreatePartsAsync(assemblies).ConfigureAwait(false)).GetAwaiter().GetResult(); - return ComposableCatalog.Create(resolver).AddParts(parts); - } - - private static IExportProviderFactory CreateExportProviderFactory(ComposableCatalog catalog) - { - var configuration = CompositionConfiguration.Create(catalog); - var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); - return runtimeComposition.CreateExportProviderFactory(); - } - private static RemoteWorkspace CreatePrimaryWorkspace() { - var catalog = CreateCatalog(RemoteHostAssemblies); - var exportProviderFactory = CreateExportProviderFactory(catalog); - var exportProvider = exportProviderFactory.CreateExportProvider(); - + var exportProvider = RemoteExportProviderBuilder.ExportProvider; return new RemoteWorkspace(VisualStudioMefHostServices.Create(exportProvider)); } @@ -143,25 +118,4 @@ public async ValueTask RunServiceAsync( return result; } - - private sealed class SimpleAssemblyLoader : IAssemblyLoader - { - public static readonly IAssemblyLoader Instance = new SimpleAssemblyLoader(); - - public Assembly LoadAssembly(AssemblyName assemblyName) - => Assembly.Load(assemblyName); - - public Assembly LoadAssembly(string assemblyFullName, string? codeBasePath) - { - var assemblyName = new AssemblyName(assemblyFullName); - if (!string.IsNullOrEmpty(codeBasePath)) - { -#pragma warning disable SYSLIB0044 // https://github.com/dotnet/roslyn/issues/71510 - assemblyName.CodeBase = codeBasePath; -#pragma warning restore SYSLIB0044 - } - - return LoadAssembly(assemblyName); - } - } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs index 27d5a0a2ebb47..b7d92866bf9b7 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs @@ -23,7 +23,8 @@ namespace Microsoft.CodeAnalysis.Remote; internal abstract partial class BrokeredServiceBase : IDisposable { protected readonly TraceSource TraceLogger; - protected readonly RemoteWorkspaceManager WorkspaceManager; + protected RemoteWorkspaceManager WorkspaceManager + => TestData?.WorkspaceManager ?? RemoteWorkspaceManager.Default; protected readonly SolutionAssetSource SolutionAssetSource; protected readonly ServiceBrokerClient ServiceBrokerClient; @@ -60,7 +61,6 @@ protected BrokeredServiceBase(in ServiceConstructionArguments arguments) TraceLogger = traceSource; TestData = (RemoteHostTestData?)arguments.ServiceProvider.GetService(typeof(RemoteHostTestData)); - WorkspaceManager = TestData?.WorkspaceManager ?? RemoteWorkspaceManager.Default; #pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed ServiceBrokerClient = new ServiceBrokerClient(arguments.ServiceBroker); diff --git a/src/Workspaces/Remote/ServiceHub/Services/Initialization/RemoteInitializationService.cs b/src/Workspaces/Remote/ServiceHub/Services/Initialization/RemoteInitializationService.cs new file mode 100644 index 0000000000000..a6ac637812202 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/Initialization/RemoteInitializationService.cs @@ -0,0 +1,39 @@ +// 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.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed class RemoteInitializationService( + BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(arguments), IRemoteInitializationService +{ + internal sealed class Factory : FactoryBase + { + protected override IRemoteInitializationService CreateService(in ServiceConstructionArguments arguments) + => new RemoteInitializationService(arguments); + } + + public async ValueTask<(int processId, string? errorMessage)> InitializeAsync(WorkspaceConfigurationOptions options, string localSettingsDirectory, CancellationToken cancellationToken) + { + // Performed before RunServiceAsync to ensure that the export provider is initialized before the RemoteWorkspaceManager is created + // as part of the RunServiceAsync call. + var errorMessage = await RemoteExportProviderBuilder.InitializeAsync(localSettingsDirectory, cancellationToken).ConfigureAwait(false); + + var processId = await RunServiceAsync(cancellationToken => + { + var service = (RemoteWorkspaceConfigurationService)GetWorkspaceServices().GetRequiredService(); + service.InitializeOptions(options); + + return ValueTaskFactory.FromResult(Process.GetCurrentProcess().Id); + }, cancellationToken).ConfigureAwait(false); + + return (processId, errorMessage); + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs index 6a8226a095a76..8bc8e89d4c6d0 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs @@ -5,12 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Telemetry; @@ -102,18 +100,4 @@ private static void SetRoslynLogger(ImmutableArray loggerTypes, Func< RoslynLogger.SetLogger(AggregateLogger.Remove(RoslynLogger.GetLogger(), l => l is T)); } } - - /// - /// Remote API. - /// - public ValueTask InitializeAsync(WorkspaceConfigurationOptions options, CancellationToken cancellationToken) - { - return RunServiceAsync(cancellationToken => - { - var service = (RemoteWorkspaceConfigurationService)GetWorkspaceServices().GetRequiredService(); - service.InitializeOptions(options); - - return ValueTaskFactory.FromResult(Process.GetCurrentProcess().Id); - }, cancellationToken); - } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs index bc4197cc204c1..a5ccfd0927d5b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Reflection; @@ -14,9 +13,9 @@ namespace Microsoft.CodeAnalysis.Host.Mef; internal static class MefHostServicesHelpers { - public static ImmutableArray LoadNearbyAssemblies(IEnumerable assemblyNames) + public static ImmutableArray LoadNearbyAssemblies(ImmutableArray assemblyNames) { - var assemblies = new List(); + var assemblies = new List(assemblyNames.Length); foreach (var assemblyName in assemblyNames) { @@ -30,12 +29,12 @@ public static ImmutableArray LoadNearbyAssemblies(IEnumerable return [.. assemblies]; } - private static Assembly TryLoadNearbyAssembly(string assemblySimpleName) + private static Assembly? TryLoadNearbyAssembly(string assemblySimpleName) { var thisAssemblyName = typeof(MefHostServicesHelpers).GetTypeInfo().Assembly.GetName(); var assemblyShortName = thisAssemblyName.Name; var assemblyVersion = thisAssemblyName.Version; - var publicKeyToken = thisAssemblyName.GetPublicKeyToken().Aggregate(string.Empty, (s, b) => s + b.ToString("x2")); + var publicKeyToken = thisAssemblyName.GetPublicKeyToken()?.Aggregate(string.Empty, (s, b) => s + b.ToString("x2")); if (string.IsNullOrEmpty(publicKeyToken)) { @@ -53,4 +52,23 @@ private static Assembly TryLoadNearbyAssembly(string assemblySimpleName) return null; } } + + public static string? TryFindNearbyAssemblyLocation(string assemblySimpleName) + { + // Try to find the assembly location by looking for a filename matching that assembly name + // at the same location as this assembly. + var thisAssemblyName = typeof(MefHostServicesHelpers).GetTypeInfo().Assembly.Location; + var thisAssemblyFolder = Path.GetDirectoryName(thisAssemblyName); + var potentialAssemblyPath = thisAssemblyFolder != null + ? Path.Combine(thisAssemblyFolder, assemblySimpleName + ".dll") + : null; + + if (File.Exists(potentialAssemblyPath)) + return potentialAssemblyPath; + + // Otherwise, fall back to loading the assembly to find the file locations + var assembly = TryLoadNearbyAssembly(assemblySimpleName); + + return assembly?.Location; + } } From 1220f77d6f17f3a1300238d748356b691dcf5eb0 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 22 Apr 2025 17:19:03 -0700 Subject: [PATCH 032/114] Hook up IDocumentServiceProvider in VS Code --- .../HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs | 2 +- ...icrosoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj | 1 + .../Razor/Features/RazorDocumentServiceProviderWrapper.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs index 96a2b692f1214..6c14e7f4466dc 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs @@ -47,7 +47,7 @@ internal sealed partial class RazorDynamicFileInfoProvider(Lazy + diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorDocumentServiceProviderWrapper.cs b/src/Tools/ExternalAccess/Razor/Features/RazorDocumentServiceProviderWrapper.cs index 6f292bc9e852b..5a4ea127f8c2a 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorDocumentServiceProviderWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorDocumentServiceProviderWrapper.cs @@ -16,7 +16,7 @@ internal sealed class RazorDocumentServiceProviderWrapper : IDocumentServiceProv // The lazily initialized service fields use StrongBox to explicitly allow null as an initialized value. private StrongBox? _lazySpanMappingService; private StrongBox? _lazyExcerptService; - private StrongBox? _lazyDocumentPropertiesService; + private StrongBox? _lazyDocumentPropertiesService; public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDocumentServiceProvider) { @@ -74,7 +74,7 @@ public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDo }, _innerDocumentServiceProvider); - return (TService?)(object?)documentPropertiesService; + return (TService?)documentPropertiesService; } return this as TService; From 4c12822294303e8b9f847e15435191459a59d2d6 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 22 Apr 2025 17:29:54 -0700 Subject: [PATCH 033/114] Using --- .../HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs index 6c14e7f4466dc..9b3e7b297838c 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Composition; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; From 77b0cd9a3373317c09d0cf4ef8c51f791061d744 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 23 Apr 2025 11:38:17 +1000 Subject: [PATCH 034/114] Allow Razor files to be additional documents in a misc files project --- .../Workspace/MiscellaneousFileUtilities.cs | 11 ++++-- .../LspMiscellaneousFilesWorkspace.cs | 8 ++++- .../LspMiscellaneousFilesWorkspaceTests.cs | 35 ++++++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs index a10159c8efe1b..f5457cf655138 100644 --- a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs +++ b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs @@ -27,7 +27,11 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( var fileExtension = PathUtilities.GetExtension(filePath); var fileName = PathUtilities.GetFileName(filePath); - var languageServices = services.GetLanguageServices(languageInformation.LanguageName); + // For Razor files we need to override the language name to C# as thats what code is generated + var isRazor = languageInformation.LanguageName == "Razor"; + var languageName = isRazor ? LanguageNames.CSharp : languageInformation.LanguageName; + + var languageServices = services.GetLanguageServices(languageName); var compilationOptions = languageServices.GetService()?.GetDefaultCompilationOptions(); // Use latest language version which is more permissive, as we cannot find out language version of the project which the file belongs to @@ -63,7 +67,7 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( version: VersionStamp.Create(), name: FeaturesResources.Miscellaneous_Files, assemblyName: assemblyName, - language: languageInformation.LanguageName, + language: languageName, compilationOutputInfo: default, checksumAlgorithm: checksumAlgorithm, // Miscellaneous files projects are never fully loaded since, by definition, it won't know @@ -71,7 +75,8 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( hasAllInformation: sourceCodeKind == SourceCodeKind.Script), compilationOptions: compilationOptions, parseOptions: parseOptions, - documents: [documentInfo], + documents: isRazor ? null : [documentInfo], + additionalDocuments: isRazor ? [documentInfo] : null, metadataReferences: metadataReferences); return projectInfo; diff --git a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs index e74031f9958a3..6baf6e5f6c967 100644 --- a/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs +++ b/src/LanguageServer/Protocol/Workspaces/LspMiscellaneousFilesWorkspace.cs @@ -36,7 +36,7 @@ internal sealed class LspMiscellaneousFilesWorkspace(ILspServices lspServices, I /// Calls to this method and are made /// from LSP text sync request handling which do not run concurrently. /// - public Document? AddMiscellaneousDocument(Uri uri, SourceText documentText, string languageId, ILspLogger logger) + public TextDocument? AddMiscellaneousDocument(Uri uri, SourceText documentText, string languageId, ILspLogger logger) { var documentFilePath = ProtocolConversions.GetDocumentFilePathFromUri(uri); @@ -63,6 +63,12 @@ internal sealed class LspMiscellaneousFilesWorkspace(ILspServices lspServices, I this, documentFilePath, sourceTextLoader, languageInformation, documentText.ChecksumAlgorithm, Services.SolutionServices, []); OnProjectAdded(projectInfo); + if (languageInformation.LanguageName == "Razor") + { + var docId = projectInfo.AdditionalDocuments.Single().Id; + return CurrentSolution.GetRequiredAdditionalDocument(docId); + } + var id = projectInfo.Documents.Single().Id; return CurrentSolution.GetRequiredDocument(id); } diff --git a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index b31700c4ea598..5c06a1171e131 100644 --- a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -180,6 +180,34 @@ void M() Assert.Null(GetMiscellaneousDocument(testLspServer)); } + [Theory, CombinatorialData] + public async Task TestLooseFile_RazorFile(bool mutatingLspWorkspace) + { + var source = "
"; + + // Create a server that supports LSP misc files and verify no misc files present. + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); + Assert.Null(GetMiscellaneousDocument(testLspServer)); + Assert.Null(GetMiscellaneousAdditionalDocument(testLspServer)); + + // Open an empty loose file and make a request to verify it gets added to the misc workspace. + var looseFileUri = ProtocolConversions.CreateAbsoluteUri(@"C:\SomeFile.razor"); + await testLspServer.OpenDocumentAsync(looseFileUri, source).ConfigureAwait(false); + + // Trigger a request and assert we got a file in the misc workspace. + await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false); + Assert.Null(GetMiscellaneousDocument(testLspServer)); + Assert.NotNull(GetMiscellaneousAdditionalDocument(testLspServer)); + + // Trigger another request and assert we got a file in the misc workspace. + await AssertFileInMiscWorkspaceAsync(testLspServer, looseFileUri).ConfigureAwait(false); + Assert.NotNull(GetMiscellaneousAdditionalDocument(testLspServer)); + + await testLspServer.CloseDocumentAsync(looseFileUri).ConfigureAwait(false); + Assert.Null(GetMiscellaneousDocument(testLspServer)); + Assert.Null(GetMiscellaneousAdditionalDocument(testLspServer)); + } + [Theory, CombinatorialData] public async Task TestLooseFile_RequestedTwiceAndClosed(bool mutatingLspWorkspace) { @@ -306,7 +334,12 @@ private static async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspSe private static Document? GetMiscellaneousDocument(TestLspServer testLspServer) { - return testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.Documents.Single(); + return testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.Documents.SingleOrDefault(); + } + + private static TextDocument? GetMiscellaneousAdditionalDocument(TestLspServer testLspServer) + { + return testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()!.CurrentSolution.Projects.SingleOrDefault()?.AdditionalDocuments.SingleOrDefault(); } private static async Task RunGetHoverAsync(TestLspServer testLspServer, LSP.Location caret) From 37bbd93d00d162c3288b47e7f9417ef159e63558 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 21:31:52 -0700 Subject: [PATCH 035/114] Enable C# classification in most code action test cases --- ...roviderBasedUserDiagnosticTest_NoEditor.cs | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs b/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs index 0afe14f093778..a47d050255166 100644 --- a/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs +++ b/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; @@ -154,4 +154,37 @@ public interface IAsyncEnumerator : IAsyncDisposable internal OptionsCollection IgnoreAllParentheses => ParenthesesOptionsProvider.IgnoreAllParentheses; internal OptionsCollection RemoveAllUnnecessaryParentheses => ParenthesesOptionsProvider.RemoveAllUnnecessaryParentheses; internal OptionsCollection RequireAllParenthesesForClarity => ParenthesesOptionsProvider.RequireAllParenthesesForClarity; + + internal new Task TestInRegularAndScript1Async( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup, + int index = 0, + TestParameters? parameters = null) + { + return base.TestInRegularAndScript1Async(initialMarkup, expectedMarkup, index, parameters); + } + + internal new Task TestInRegularAndScript1Async( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup, + TestParameters parameters) + { + return base.TestInRegularAndScript1Async(initialMarkup, expectedMarkup, parameters); + } + + protected new Task TestMissingInRegularAndScriptAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, + TestParameters? parameters = null, + int codeActionIndex = 0) + { + return base.TestMissingInRegularAndScriptAsync(initialMarkup, parameters, codeActionIndex); + } + + protected new Task TestMissingAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup, + TestParameters? parameters = null, + int codeActionIndex = 0) + { + return base.TestMissingAsync(initialMarkup, parameters, codeActionIndex); + } } From 342e06e4e5d85171c57e3144d42f0be3ee9bf7da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 22 Apr 2025 22:02:42 -0700 Subject: [PATCH 036/114] nrt --- ...pDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs b/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs index a47d050255166..903b4fb9c6277 100644 --- a/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs +++ b/src/Features/CSharpTest/Diagnostics/AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs @@ -11,15 +11,11 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; -public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor : AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor +public abstract partial class AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(ITestOutputHelper? logger) + : AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger) { private static readonly CSharpParseOptions Script = new(kind: SourceCodeKind.Script); - protected AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(ITestOutputHelper logger) - : base(logger) - { - } - protected override ParseOptions GetScriptOptions() => Script; protected internal override string GetLanguage() => LanguageNames.CSharp; From 71fee609624974e5e25b7b5d69de1c9f96137f61 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Wed, 23 Apr 2025 09:54:53 +0200 Subject: [PATCH 037/114] Update ICSharpCode.Decompiler to 9.1.0.7988 --- eng/Directory.Packages.props | 2 +- .../Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 9a7c8c1049c59..d29bb7f891a7c 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -231,7 +231,7 @@ - + + + + + - + https://github.com/dotnet/source-build-reference-packages - 4b566314cf9602b77f25538a88b9c8175231c106 + 19eb5ea4e5f9c4e5256843a92805c8c9e942207d From b2859df461ab4fac88472df809706c9dbd81beb6 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 23 Apr 2025 15:52:16 -0700 Subject: [PATCH 047/114] Remove unused reference --- eng/Directory.Packages.props | 1 - .../Microsoft.CodeAnalysis.LanguageServer.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 9a7c8c1049c59..6ee9d8cca62ee 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -145,7 +145,6 @@ - - true From 337e312350dafae0c8a28563caef0f28c3c738f1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 25 Apr 2025 14:21:09 +1000 Subject: [PATCH 059/114] Remove IRazorCohostDynamicRegistrationService --- .../IRazorCohostDynamicRegistrationService.cs | 15 --------------- .../Features/Cohost/RazorStartupServiceFactory.cs | 13 ++----------- 2 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs deleted file mode 100644 index ea71386303872..0000000000000 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCohostDynamicRegistrationService.cs +++ /dev/null @@ -1,15 +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; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; - -[Obsolete($"Use {nameof(ICohostStartupService)} instead")] -internal interface IRazorCohostDynamicRegistrationService -{ - Task RegisterAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); -} diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs index 35563e980fa39..1c9501ea4bb1a 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs @@ -22,19 +22,15 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class RazorStartupServiceFactory( [Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService, - [Import(AllowDefault = true)] Lazy? dynamicRegistrationService, [Import(AllowDefault = true)] Lazy? cohostStartupService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { - return new RazorStartupService(uIContextActivationService, dynamicRegistrationService, cohostStartupService); + return new RazorStartupService(uIContextActivationService, cohostStartupService); } private class RazorStartupService( IUIContextActivationService? uIContextActivationService, -#pragma warning disable CS0618 // Type or member is obsolete - Lazy? dynamicRegistrationService, -#pragma warning restore CS0618 // Type or member is obsolete Lazy? cohostStartupService) : ILspService, IOnInitialized, IDisposable { private readonly CancellationTokenSource _disposalTokenSource = new(); @@ -55,7 +51,7 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon return Task.CompletedTask; } - if (dynamicRegistrationService is null && cohostStartupService is null) + if (cohostStartupService is null) { return Task.CompletedTask; } @@ -96,11 +92,6 @@ private async Task InitializeRazorAsync(ClientCapabilities clientCapabilities, R { await cohostStartupService.Value.StartupAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); } - - if (dynamicRegistrationService is not null) - { - await dynamicRegistrationService.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); - } } } } From 7598d96c37fb16b37cf9ff46ca08f3a692799897 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 25 Apr 2025 14:21:46 +1000 Subject: [PATCH 060/114] Remove IRazorCustomMessageTarget --- .../Features/Cohost/IRazorCustomMessageTarget.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCustomMessageTarget.cs diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCustomMessageTarget.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCustomMessageTarget.cs deleted file mode 100644 index 0f2a881500ed1..0000000000000 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/IRazorCustomMessageTarget.cs +++ /dev/null @@ -1,12 +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; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; - -[Obsolete("This should no longer be used")] -internal interface IRazorCustomMessageTarget -{ -} From 2c28c97bf8a6c29fe71b802a9ab49308e6119ea8 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 25 Apr 2025 15:40:40 +1000 Subject: [PATCH 061/114] Missed one! --- ...ohostClientLanguageServerManagerFactory.cs | 24 ------------------- .../IRazorClientLanguageServerManager.cs | 4 ---- .../RazorClientLanguageServerManager.cs | 2 +- 3 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 src/Tools/ExternalAccess/Razor/Features/Cohost/RazorCohostClientLanguageServerManagerFactory.cs diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorCohostClientLanguageServerManagerFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorCohostClientLanguageServerManagerFactory.cs deleted file mode 100644 index 67480de030be4..0000000000000 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorCohostClientLanguageServerManagerFactory.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; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; - -[Shared] -[ExportCohostLspServiceFactory(typeof(IRazorCohostClientLanguageServerManager))] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class RazorCohostClientLanguageServerManagerFactory() : ILspServiceFactory -{ - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - { - var notificationManager = lspServices.GetRequiredService(); - - return new RazorClientLanguageServerManager(notificationManager); - } -} diff --git a/src/Tools/ExternalAccess/Razor/Features/IRazorClientLanguageServerManager.cs b/src/Tools/ExternalAccess/Razor/Features/IRazorClientLanguageServerManager.cs index 1885c5e887230..5d4b5b05c56a1 100644 --- a/src/Tools/ExternalAccess/Razor/Features/IRazorClientLanguageServerManager.cs +++ b/src/Tools/ExternalAccess/Razor/Features/IRazorClientLanguageServerManager.cs @@ -16,7 +16,3 @@ internal interface IRazorClientLanguageServerManager : ILspService Task SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken); ValueTask SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken); } - -// TODO: Remove this interface once razor inserts and moves off of it. -internal interface IRazorCohostClientLanguageServerManager : IRazorClientLanguageServerManager -{ } diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorClientLanguageServerManager.cs b/src/Tools/ExternalAccess/Razor/Features/RazorClientLanguageServerManager.cs index aa9caa4c8b8ad..6c351404eb6b9 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorClientLanguageServerManager.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorClientLanguageServerManager.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; -internal class RazorClientLanguageServerManager(IClientLanguageServerManager clientLanguageServerManager) : IRazorCohostClientLanguageServerManager +internal class RazorClientLanguageServerManager(IClientLanguageServerManager clientLanguageServerManager) : IRazorClientLanguageServerManager { public Task SendRequestAsync(string methodName, TParams @params, CancellationToken cancellationToken) => clientLanguageServerManager.SendRequestAsync(methodName, @params, cancellationToken); From 567495e41fa4fc0eb9ffa5639b7a5461baeec6d0 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 25 Apr 2025 08:28:48 -0700 Subject: [PATCH 062/114] Switch Project data structures from ImmutableDictionary => Dictionary and lock (#78287) Solution/Project classes keep several immutable dictionaries for mapping (Document/Project)Id to various data. ImmutableDictionaries are a fine (but slow) data structure when data between versions is to be shared, but none of the values in these ImmutableDictionaries persist between solution/project transformations. In fact, the only operation performed on these dictionaries is GetOrAdd calls. As immutable dictionaries are quite poor performing wrt lookups/adds, I thought I would try out two approaches to see if they showed improvements: Switch to ConcurrentDictionary (see *** WIP: Switch Project data structures from ImmutableDictionary => ConcurrentDictionary #78285). Switch to simple Dictionaries guarded by locks (this PR) I created test insertions for each of these PRs to gather speedometer numbers, particularly during the solution load of the c# editing speedometer test. The measurements below are from GC Heap Allocs and CPU samples for those tests under (Solution/Project).GetDocument calls ConcurrentDictionary change test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/631277 Dictionary/lock test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/631278 *** Baseline *** Alloc: 120 MB (1.0%) CPU: 560 ms (0.3%) *** Concurrent Dictionary *** Alloc: 59 MB CPU: 258 ms (0.1%) *** Dictionary with lock *** Alloc: 28 MB CPU: 172 ms (< 0.1%) So, it looks like the best performing of the bunch is this PR, which uses simple dictionaries and locks, thus why I'm elevating this PR out of draft mode. The ConcurrentDictionary PR is interesting as the code changes are simple and do give a nice perf improvement. --- .../Portable/Workspace/Solution/Project.cs | 54 ++++++++++++++----- .../Portable/Workspace/Solution/Solution.cs | 11 ++-- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index f8f4981ead216..d20005fd748bb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -26,10 +26,11 @@ namespace Microsoft.CodeAnalysis; [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] public partial class Project { - private ImmutableDictionary _idToDocumentMap = ImmutableDictionary.Empty; - private ImmutableDictionary _idToSourceGeneratedDocumentMap = ImmutableDictionary.Empty; - private ImmutableDictionary _idToAdditionalDocumentMap = ImmutableDictionary.Empty; - private ImmutableDictionary _idToAnalyzerConfigDocumentMap = ImmutableDictionary.Empty; + // Only access these dictionaries when holding them as a lock + private Dictionary? _idToDocumentMap; + private Dictionary? _idToSourceGeneratedDocumentMap; + private Dictionary? _idToAdditionalDocumentMap; + private Dictionary? _idToAnalyzerConfigDocumentMap; internal Project(Solution solution, ProjectState projectState) { @@ -239,19 +240,48 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) /// Get the document in this project with the specified document Id. /// public Document? GetDocument(DocumentId documentId) - => ImmutableInterlocked.GetOrAdd(ref _idToDocumentMap, documentId, s_tryCreateDocumentFunction, this); + => GetOrAddDocumentUnderLock(documentId, ref _idToDocumentMap, s_tryCreateDocumentFunction, this); /// /// Get the additional document in this project with the specified document Id. /// public TextDocument? GetAdditionalDocument(DocumentId documentId) - => ImmutableInterlocked.GetOrAdd(ref _idToAdditionalDocumentMap, documentId, s_tryCreateAdditionalDocumentFunction, this); + => GetOrAddDocumentUnderLock(documentId, ref _idToAdditionalDocumentMap, s_tryCreateAdditionalDocumentFunction, this); /// /// Get the analyzer config document in this project with the specified document Id. /// public AnalyzerConfigDocument? GetAnalyzerConfigDocument(DocumentId documentId) - => ImmutableInterlocked.GetOrAdd(ref _idToAnalyzerConfigDocumentMap, documentId, s_tryCreateAnalyzerConfigDocumentFunction, this); + => GetOrAddDocumentUnderLock(documentId, ref _idToAnalyzerConfigDocumentMap, s_tryCreateAnalyzerConfigDocumentFunction, this); + + private static TDocument GetOrAddDocumentUnderLock(DocumentId documentId, ref Dictionary? idMap, Func tryCreate, TArg arg) + { + if (idMap == null) + { + // First call assigns a new dictionary. Any other simultaneous requests will not assign the + // dictionary they created to idMap. At that point, normal locking rules apply. + Interlocked.CompareExchange(ref idMap, new Dictionary(), null); + } + + lock (idMap) + { + return idMap.GetOrAdd(documentId, tryCreate, arg); + } + } + + private static bool TryGetDocumentValueUnderLock(DocumentId documentId, ref Dictionary? idMap, out TDocument? document) + { + if (idMap == null) + { + document = default; + return false; + } + + lock (idMap) + { + return idMap.TryGetValue(documentId, out document); + } + } /// /// Gets a document or a source generated document in this solution with the specified document ID. @@ -288,7 +318,7 @@ public async ValueTask> GetSourceGeneratedD // return an iterator to avoid eagerly allocating all the document instances return generatedDocumentStates.States.Values.Select(state => - ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))); + GetOrAddDocumentUnderLock(state.Id, ref _idToSourceGeneratedDocumentMap, s_createSourceGeneratedDocumentFunction, (state, this))); } internal async IAsyncEnumerable GetAllRegularAndSourceGeneratedDocumentsAsync([EnumeratorCancellation] CancellationToken cancellationToken) @@ -312,7 +342,7 @@ internal async IAsyncEnumerable GetAllRegularAndSourceGeneratedDocumen return null; // Quick check first: if we already have created a SourceGeneratedDocument wrapper, we're good - if (_idToSourceGeneratedDocumentMap.TryGetValue(documentId, out var sourceGeneratedDocument)) + if (TryGetDocumentValueUnderLock(documentId, ref _idToSourceGeneratedDocumentMap, out var sourceGeneratedDocument)) return sourceGeneratedDocument; // We'll have to run generators if we haven't already and now try to find it. @@ -325,7 +355,7 @@ internal async IAsyncEnumerable GetAllRegularAndSourceGeneratedDocumen } internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGeneratedDocumentState state) - => ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)); + => GetOrAddDocumentUnderLock(state.Id, ref _idToSourceGeneratedDocumentMap, s_createSourceGeneratedDocumentFunction, (state, this)); /// /// Returns the for a source generated document that has already been generated and observed. @@ -346,7 +376,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera return null; // Easy case: do we already have the SourceGeneratedDocument created? - if (_idToSourceGeneratedDocumentMap.TryGetValue(documentId, out var document)) + if (TryGetDocumentValueUnderLock(documentId, ref _idToSourceGeneratedDocumentMap, out var document)) return document; // Trickier case now: it's possible we generated this, but we don't actually have the SourceGeneratedDocument for it, so let's go @@ -355,7 +385,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera if (documentState == null) return null; - return ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, s_createSourceGeneratedDocumentFunction, (documentState, this)); + return GetOrAddDocumentUnderLock(documentId, ref _idToSourceGeneratedDocumentMap, s_createSourceGeneratedDocumentFunction, (documentState, this)); } internal ValueTask> GetSourceGeneratorDiagnosticsAsync(CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 0ad1c652735c5..20f5a637864e8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -27,9 +27,8 @@ namespace Microsoft.CodeAnalysis; /// public partial class Solution { - - // Values for all these are created on demand. - private ImmutableDictionary _projectIdToProjectMap; + // Values for all these are created on demand. Only access when holding the dictionary as a lock. + private readonly Dictionary _projectIdToProjectMap = []; /// /// Result of calling . @@ -46,7 +45,6 @@ private Solution( SolutionCompilationState compilationState, AsyncLazy? cachedFrozenSolution = null) { - _projectIdToProjectMap = ImmutableDictionary.Empty; CompilationState = compilationState; _cachedFrozenSolution = cachedFrozenSolution ?? @@ -152,7 +150,10 @@ public bool ContainsProject([NotNullWhen(returnValue: true)] ProjectId? projectI { if (this.ContainsProject(projectId)) { - return ImmutableInterlocked.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this); + lock (_projectIdToProjectMap) + { + return _projectIdToProjectMap.GetOrAdd(projectId, s_createProjectFunction, this); + } } return null; From e8740a402d41ccf99dfb6e7e185178f066e116b6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:27:23 +0000 Subject: [PATCH 063/114] [main] Source code updates from dotnet/dotnet (#78309) * Update dependencies from https://github.com/dotnet/dotnet --------- Co-authored-by: dotnet-maestro[bot] Co-authored-by: Viktor Hofer --- eng/Version.Details.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 04bb613f2a5a5..a620660ce5ea1 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,5 +1,6 @@ + From 9f6dff89673dbaac6ef832b36727aad81802045f Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Fri, 25 Apr 2025 12:32:48 -0700 Subject: [PATCH 064/114] Ensure we generate a full package for LanguageServices.Xaml This otherwise overrides everything and creates only a symbol package. --- .../Impl/Microsoft.VisualStudio.LanguageServices.Xaml.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/VisualStudio/Xaml/Impl/Microsoft.VisualStudio.LanguageServices.Xaml.csproj b/src/VisualStudio/Xaml/Impl/Microsoft.VisualStudio.LanguageServices.Xaml.csproj index f4d4a9c6f8cdd..e7aea4d8b5e72 100644 --- a/src/VisualStudio/Xaml/Impl/Microsoft.VisualStudio.LanguageServices.Xaml.csproj +++ b/src/VisualStudio/Xaml/Impl/Microsoft.VisualStudio.LanguageServices.Xaml.csproj @@ -7,7 +7,6 @@ false net472 None - true true From f074f97ea011857cabae91a2aa103c2b7032e1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Fri, 25 Apr 2025 14:42:18 -0700 Subject: [PATCH 065/114] Remove Semantic Search inline Copilot prototype (#78323) --- .../SemanticSearchToolWindowImpl.cs | 209 +----------------- .../Def/Options/VisualStudioOptionStorage.cs | 1 - .../SemanticSearchFeatureFlag.cs | 1 - .../SemanticSearchCopilotUIProviderWrapper.cs | 38 ---- 4 files changed, 6 insertions(+), 243 deletions(-) delete mode 100644 src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs index c0daee8127246..c2a2e4dffec44 100644 --- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs @@ -4,7 +4,6 @@ using System; using System.Composition; -using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -16,7 +15,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Navigation; @@ -59,8 +57,6 @@ internal sealed partial class SemanticSearchToolWindowImpl( VisualStudioWorkspace workspace, IStreamingFindUsagesPresenter resultsPresenter, ITextUndoHistoryRegistry undoHistoryRegistry, - ISemanticSearchCopilotService copilotService, - Lazy copilotUIProvider, // lazy to avoid loading Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot IVsService vsUIShellProvider) : IDisposable { private const int ToolBarHeight = 26; @@ -120,9 +116,7 @@ private async Task CreateContentAsync(CancellationToken cancel var vsUIShell = await vsUIShellProvider.GetValueAsync(cancellationToken).ConfigureAwait(false); - var copilotUI = CreateCopilotUI(); - - var textViewHost = CreateTextViewHost(vsUIShell, copilotUI); + var textViewHost = CreateTextViewHost(vsUIShell); var textViewControl = textViewHost.HostControl; _textView = textViewHost.TextView; _textBuffer = textViewHost.TextView.TextBuffer; @@ -167,11 +161,6 @@ private async Task CreateContentAsync(CancellationToken cancel toolWindowGrid.Children.Add(toolbarGrid); - if (copilotUI != null) - { - toolWindowGrid.Children.Add(copilotUI.Control); - } - toolWindowGrid.Children.Add(textViewControl); toolbarGrid.Children.Add(executeButton); toolbarGrid.Children.Add(cancelButton); @@ -181,12 +170,6 @@ private async Task CreateContentAsync(CancellationToken cancel Grid.SetRow(toolbarGrid, 0); Grid.SetColumn(toolbarGrid, 0); - if (copilotUI != null) - { - Grid.SetRow(copilotUI.Control, 1); - Grid.SetColumn(copilotUI.Control, 0); - } - Grid.SetRow(textViewControl, 2); Grid.SetColumn(textViewControl, 0); @@ -205,106 +188,6 @@ private async Task CreateContentAsync(CancellationToken cancel return toolWindowGrid; } - private CopilotUI? CreateCopilotUI() - { - if (!copilotUIProvider.Value.IsAvailable || !copilotService.IsAvailable) - { - return null; - } - - if (!globalOptions.GetOption(SemanticSearchFeatureFlag.PromptEnabled)) - { - return null; - } - - var outerGrid = new Grid() - { - Background = (Brush)Application.Current.FindResource(CommonControlsColors.TextBoxBackgroundBrushKey), - }; - - ImageThemingUtilities.SetImageBackgroundColor(outerGrid, (Color)Application.Current.Resources[CommonDocumentColors.PageBackgroundColorKey]); - ThemedDialogStyleLoader.SetUseDefaultThemedDialogStyles(outerGrid, true); - - // [ prompt border | empty ] - outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); - outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); - - var promptGrid = new Grid(); - - // [ input | panel ] - promptGrid.ColumnDefinitions.Add(new ColumnDefinition { MaxWidth = 600, Width = GridLength.Auto }); - promptGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); - - var promptTextBox = copilotUIProvider.Value.GetTextBox(); - - var panel = new StackPanel() - { - Orientation = Orientation.Horizontal, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Bottom, - Margin = new Thickness(8, 8, 0, 8), - }; - - Grid.SetColumn(promptTextBox.Control, 0); - promptGrid.Children.Add(promptTextBox.Control); - - Grid.SetColumn(panel, 1); - promptGrid.Children.Add(panel); - - var promptGridBorder = new Border - { - Name = "PromptBorder", - BorderBrush = (Brush)Application.Current.Resources[EnvironmentColors.SystemHighlightBrushKey], - BorderThickness = new Thickness(1), - Child = promptGrid - }; - - Grid.SetColumn(promptGridBorder, 0); - outerGrid.Children.Add(promptGridBorder); - - // ComboBox for model selection - var modelPicker = new ComboBox - { - SelectedIndex = 0, - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, - Margin = new Thickness(4, 0, 4, 0), - Height = 24, - IsEditable = false, - IsReadOnly = true, - BorderThickness = new Thickness(0), - MinHeight = 24, - VerticalContentAlignment = VerticalAlignment.Top, - TabIndex = 1, - Style = (Style)Application.Current.FindResource(VsResourceKeys.ComboBoxStyleKey) - }; - - modelPicker.Items.Add("gpt-4o"); - modelPicker.Items.Add("gpt-4o-mini"); - modelPicker.Items.Add("o1"); - modelPicker.Items.Add("o1-ga"); - modelPicker.Items.Add("o1-mini"); - - panel.Children.Add(modelPicker); - - var submitButton = CreateButton( - KnownMonikers.Send, - automationName: "Generate query", - acceleratorKey: "Ctrl+Enter", - toolTip: "Generate query"); - - panel.Children.Add(submitButton); - - submitButton.Click += (_, _) => SubmitCopilotQuery(promptTextBox.Text, modelPicker.Text); - - return new CopilotUI() - { - Control = outerGrid, - Input = promptTextBox, - ModelPicker = modelPicker, - }; - } - private static Button CreateButton( Imaging.Interop.ImageMoniker moniker, string automationName, @@ -383,7 +266,7 @@ private static ControlTemplate CreateButtonTemplate() """, context); } - private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell, CopilotUI? copilotUI) + private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell) { Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); @@ -422,7 +305,7 @@ private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell, CopilotUI? cop ErrorHandler.ThrowOnFailure(windowFrame.SetProperty((int)__VSFPROPID.VSFPROPID_ViewHelper, textViewAdapter)); - _ = new CommandFilter(this, textViewAdapter, copilotUI); + _ = new CommandFilter(this, textViewAdapter); return textViewHost; } @@ -448,62 +331,6 @@ private void UpdateUIState() _cancelButton.IsEnabled = isExecuting; } - private void SubmitCopilotQuery(string input, string model) - { - Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); - Contract.ThrowIfNull(_textBuffer); - Contract.ThrowIfNull(copilotService); - - // TODO: hook up cancel button for copilot queries - var cancellationSource = new CancellationTokenSource(); - - // TODO: fade out current content and show overlay spinner - - var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchToolWindow) + "." + nameof(SubmitCopilotQuery)); - _ = ExecuteAsync(cancellationSource.Token).ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken); - - async Task ExecuteAsync(CancellationToken cancellationToken) - { - await TaskScheduler.Default; - - SemanticSearchCopilotGeneratedQuery query; - - // TODO: generate list from SemanticSearch.ReferenceAssemblies: - var codeAnalysisVersion = new Version(4, 14, 0); - var sdkVersion = new Version(9, 0, 0); - - var context = new SemanticSearchCopilotContext() - { - ModelName = model, - AvailablePackages = - [ - ("Microsoft.CodeAnalysis", codeAnalysisVersion), - ("Microsoft.CodeAnalysis.CSharp", codeAnalysisVersion), - ("System.Collections.Immutable", sdkVersion), - ("System.Collections", sdkVersion), - ("System.Linq", sdkVersion), - ("System.Runtime", sdkVersion), - ] - }; - - try - { - query = await copilotService.TryGetQueryAsync(input, context, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - return; - } - catch (OperationCanceledException) - { - return; - } - - await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); - SetEditorText(query.Text); - } - } - /// /// Replaces current text buffer content with specified . Allow using Ctrl+Z to revert to the previous content. /// Must be invoked on UI thread. @@ -613,36 +440,19 @@ public NavigableLocation GetNavigableLocation(TextSpan textSpan) return true; }); - private sealed class CopilotUI - { - public required FrameworkElement Control { get; init; } - public required ITextBoxControl Input { get; init; } - public required ComboBox ModelPicker { get; init; } - } - private sealed class CommandFilter : IOleCommandTarget { private readonly SemanticSearchToolWindowImpl _window; private readonly IOleCommandTarget _editorCommandTarget; - private readonly CopilotUI? _copilotUI; - public CommandFilter(SemanticSearchToolWindowImpl window, IVsTextView textView, CopilotUI? copilotUI) + public CommandFilter(SemanticSearchToolWindowImpl window, IVsTextView textView) { _window = window; - _copilotUI = copilotUI; ErrorHandler.ThrowOnFailure(textView.AddCommandFilter(this, out _editorCommandTarget)); } - [MemberNotNullWhen(true, nameof(_copilotUI))] - private bool HasCopilotInputFocus - => _copilotUI?.Input.View.HasAggregateFocus == true; - public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) - { - var target = HasCopilotInputFocus ? _copilotUI.Input.CommandTarget : _editorCommandTarget; - - return target.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); - } + => _editorCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { @@ -651,12 +461,6 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv switch ((VSConstants.VSStd2KCmdID)nCmdID) { case VSConstants.VSStd2KCmdID.OPENLINEABOVE: - if (HasCopilotInputFocus) - { - _window.SubmitCopilotQuery(_copilotUI.Input.Text, _copilotUI.ModelPicker.Text); - return VSConstants.S_OK; - } - if (!_window.IsExecutingUIState()) { _window.RunQuery(); @@ -676,8 +480,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv } } - var target = HasCopilotInputFocus ? _copilotUI.Input.CommandTarget : _editorCommandTarget; - return target.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + return _editorCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); } } } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index bf6719c76366f..ad1367fca50a6 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -368,7 +368,6 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_unsupported_report_invalid_json_patterns", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ReportInvalidJsonPatterns")}, {"visual_studio_enable_key_binding_reset", new FeatureFlagStorage("Roslyn.KeybindingResetEnabled")}, {"visual_studio_enable_semantic_search", new FeatureFlagStorage("Roslyn.SemanticSearchEnabled")}, - {"visual_studio_enable_semantic_search_prompt", new FeatureFlagStorage("Roslyn.ShowPromptInSemanticSearch")}, {"visual_studio_enable_copilot_rename_context", new FeatureFlagStorage("Roslyn.CopilotRenameGetContext")}, {"visual_studio_key_binding_needs_reset", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeedsReset")}, {"visual_studio_key_binding_reset_never_show_again", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeverShowAgain")}, diff --git a/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs b/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs index caf12828d2004..4b5e4cadb7d7d 100644 --- a/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs +++ b/src/VisualStudio/Core/Def/SemanticSearch/SemanticSearchFeatureFlag.cs @@ -9,7 +9,6 @@ namespace Microsoft.VisualStudio.LanguageServices; internal static class SemanticSearchFeatureFlag { public static readonly Option2 Enabled = new("visual_studio_enable_semantic_search", defaultValue: false); - public static readonly Option2 PromptEnabled = new("visual_studio_enable_semantic_search_prompt", defaultValue: false); /// /// Context id that indicates that Semantic Search feature is enabled. diff --git a/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs b/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs deleted file mode 100644 index 12966f1629140..0000000000000 --- a/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs +++ /dev/null @@ -1,38 +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; -using System.Composition; -using System.Windows.Controls; -using Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.SemanticSearch; -using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch; - -[Export(typeof(ISemanticSearchCopilotUIProvider)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class SemanticSearchCopilotUIProviderWrapper( - [Import(AllowDefault = true)] Lazy? impl) : ISemanticSearchCopilotUIProvider -{ - private sealed class TextBoxWrapper(ITextBoxControlImpl impl) : ITextBoxControl - { - Control ITextBoxControl.Control => impl.Control; - string ITextBoxControl.Text { get => impl.Text; set => impl.Text = value; } - IOleCommandTarget ITextBoxControl.CommandTarget => impl.CommandTarget; - IWpfTextView ITextBoxControl.View => impl.View; - } - - bool ISemanticSearchCopilotUIProvider.IsAvailable - => impl != null; - - ITextBoxControl ISemanticSearchCopilotUIProvider.GetTextBox() - { - Contract.ThrowIfNull(impl); - return new TextBoxWrapper(impl.Value.GetTextBox()); - } -} From d96afd79f484f8d6d9b94e79e0a32597900411ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Fri, 25 Apr 2025 21:46:36 -0700 Subject: [PATCH 066/114] Update ApplyUpdates API to account for no-effect changes (#78220) --- .../PooledObjects/ArrayBuilder.cs | 6 +- .../EditAndContinueLanguageService.cs | 15 +- .../Debugger/GlassTestsHotReloadService.cs | 2 +- .../EditAndContinueLanguageServiceTests.cs | 23 +- .../EditAndContinue/DebuggingSession.cs | 4 +- .../EditAndContinueDiagnosticDescriptors.cs | 11 +- .../EditAndContinue/EditAndContinueService.cs | 2 +- .../EmitSolutionUpdateResults.cs | 252 ++++++++---- .../IEditAndContinueService.cs | 2 +- .../Remote/IRemoteEditAndContinueService.cs | 2 +- .../Remote/RemoteDebuggingSessionProxy.cs | 8 +- .../EditAndContinue/RudeEditDiagnostic.cs | 8 +- .../EditAndContinue/RunningProjectInfo.cs | 24 ++ .../API/UnitTestingHotReloadService.cs | 2 +- .../Watch/Api/WatchHotReloadService.cs | 154 ++++++- .../EditAndContinueWorkspaceServiceTests.cs | 252 ++++++++++-- .../EmitSolutionUpdateResultsTests.cs | 376 +++++++++++++++--- .../RemoteEditAndContinueServiceTests.cs | 13 +- .../WatchHotReloadServiceTests.cs | 53 +-- .../EditAndContinueWorkspaceTestBase.cs | 4 +- .../EditAndContinue/Extensions.cs | 32 +- .../MockEditAndContinueService.cs | 4 +- .../ManagedHotReloadLanguageService.cs | 12 +- .../RemoteEditAndContinueService.cs | 7 +- 24 files changed, 985 insertions(+), 283 deletions(-) create mode 100644 src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index 35ab00e03a50b..159cb02f1d3c0 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -306,7 +306,7 @@ public void Sort() _builder.Sort(); } - public void Sort(IComparer comparer) + public void Sort(IComparer? comparer) { _builder.Sort(comparer); } @@ -684,13 +684,15 @@ public void RemoveDuplicates() set.Free(); } - public void SortAndRemoveDuplicates(IComparer comparer) + public void SortAndRemoveDuplicates(IComparer? comparer = null) { if (Count <= 1) { return; } + comparer ??= Comparer.Default; + Sort(comparer); int j = 0; diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs index 34add1bee8489..5ca93c60e330a 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics; @@ -375,8 +376,12 @@ public async ValueTask GetUpdatesAsync(ImmutableArray.GetInstance(out var runningProjectPaths); runningProjectPaths.AddAll(runningProjects); - var runningProjectIds = solution.Projects.Where(p => p.FilePath != null && runningProjectPaths.Contains(p.FilePath)).Select(static p => p.Id).ToImmutableHashSet(); - var result = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, runningProjectIds, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + // 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); switch (result.ModuleUpdates.Status) { @@ -399,9 +404,9 @@ public async ValueTask GetUpdatesAsync(ImmutableArray GetProjectPaths(ImmutableArray ids) - => ids.SelectAsArray(static (id, solution) => solution.GetRequiredProject(id).FilePath!, solution); + ImmutableArray GetProjectPaths(IEnumerable ids) + => ids.SelectAsArray(id => solution.GetRequiredProject(id).FilePath!); } } diff --git a/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs b/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs index d021b1b841fc7..f0c4168d22a9d 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: [], s_noActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); + 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(), [], []); } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs index 0cd09d6134681..65eb90e8d5a6c 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; @@ -115,11 +116,9 @@ public async Task Test(bool commitChanges) var localService = localWorkspace.GetService(); - DocumentId documentId; await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution - .AddTestProject("proj", out var projectId).Solution - .AddMetadataReferences(projectId, TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)) - .AddDocument(documentId = DocumentId.CreateNewId(projectId), "test.cs", SourceText.From("class C { }", Encoding.UTF8), filePath: "test.cs")); + .AddTestProject("proj", out var projectId) + .AddTestDocument("test.cs", "class C { }", out var documentId).Project.Solution); var solution = localWorkspace.CurrentSolution; var project = solution.GetRequiredProject(projectId); @@ -159,7 +158,7 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); - mockEncService.EmitSolutionUpdateImpl = (solution, runningProjects, _) => + mockEncService.EmitSolutionUpdateImpl = (solution, _, _) => { var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!; @@ -176,7 +175,7 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution RudeEdits = [new ProjectDiagnostics(project.Id, [rudeEditDiagnostic])], SyntaxError = syntaxError, ProjectsToRebuild = [project.Id], - ProjectsToRestart = [project.Id] + ProjectsToRestart = ImmutableDictionary>.Empty.Add(project.Id, []) }; }; @@ -186,16 +185,16 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution AssertEx.Equal( [ - $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", - $"Error ENC1001: proj.csproj(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}" + $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", + $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}" ], sessionState.ApplyChangesDiagnostics.Select(Inspect)); AssertEx.Equal( [ - $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", - $"Error ENC1001: proj.csproj(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", - $"Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}", - $"RestartRequired ENC0033: test.cs(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}" + $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", + $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", + $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}", + $"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}" ], updates.Diagnostics.Select(Inspect)); Assert.True(sessionState.IsSessionActive); diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index c66e8c279ce41..48d4809726805 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -512,7 +512,7 @@ CommittedSolution.DocumentState.Indeterminate or public async ValueTask EmitSolutionUpdateAsync( Solution solution, - IImmutableSet runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { @@ -553,7 +553,7 @@ public async ValueTask EmitSolutionUpdateAsync( } using var _ = ArrayBuilder.GetInstance(out var rudeEditDiagnostics); - foreach (var (projectId, projectRudeEdits) in solutionUpdate.DocumentsWithRudeEdits.GroupBy(static e => e.DocumentId.ProjectId)) + foreach (var (projectId, projectRudeEdits) in solutionUpdate.DocumentsWithRudeEdits.GroupBy(static e => e.DocumentId.ProjectId).OrderBy(static id => id)) { foreach (var (documentId, rudeEdits) in projectRudeEdits) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 33cc69f123215..65a6f9c4410dd 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -4,10 +4,11 @@ #nullable disable +using System; using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.EditAndContinue; @@ -45,7 +46,7 @@ void add(int index, int id, string resourceName, LocalizableResourceString title } builder[index] = new DiagnosticDescriptor( - $"ENC{id:D4}", + GetDiagnosticId(id), title, messageFormat: new LocalizableResourceString(resourceName, FeaturesResources.ResourceManager, typeof(FeaturesResources)), DiagnosticCategory.EditAndContinue, @@ -210,4 +211,10 @@ private static int GetDescriptorIndex(RudeEditKind kind) private static int GetDescriptorIndex(EditAndContinueErrorCode errorCode) => s_diagnosticBaseIndex + (int)errorCode; + + private static string GetDiagnosticId(int id) + => $"ENC{id:D4}"; + + public static RudeEditKind GetRudeEditKind(string diagnosticId) + => diagnosticId.StartsWith("ENC", StringComparison.Ordinal) && int.TryParse(diagnosticId[3..], out var id) ? (RudeEditKind)id : RudeEditKind.None; } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index 4974ec41aba52..227931eec072e 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, - IImmutableSet runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs index 1a0e49782101a..5478af5286b7b 100644 --- a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs @@ -33,7 +33,7 @@ internal readonly struct Data public required DiagnosticData? SyntaxError { get; init; } [DataMember] - public required ImmutableArray ProjectsToRestart { get; init; } + public required ImmutableDictionary> ProjectsToRestart { get; init; } [DataMember] public required ImmutableArray ProjectsToRebuild { get; init; } @@ -84,7 +84,7 @@ internal ImmutableArray GetAllDiagnostics() Diagnostics = [], RudeEdits = [], SyntaxError = null, - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, ProjectsToRebuild = [], }; @@ -108,7 +108,16 @@ internal ImmutableArray GetAllDiagnostics() public required ImmutableArray RudeEdits { get; init; } public required Diagnostic? SyntaxError { get; init; } - public required ImmutableArray ProjectsToRestart { get; init; } + /// + /// Running projects that have to be restarted and a list of projects with rude edits that caused the restart. + /// + public required ImmutableDictionary> ProjectsToRestart { get; init; } + + /// + /// Projects whose source have been updated and need to be rebuilt. Does not include projects without change that depend on such projects. + /// It is assumed that the host automatically rebuilds all such projects that need rebuilding because it detects the dependent project outputs have been updated. + /// Unordered set. + /// public required ImmutableArray ProjectsToRebuild { get; init; } public Data Dehydrate() @@ -119,7 +128,7 @@ public Data Dehydrate() Diagnostics = [], RudeEdits = [], SyntaxError = null, - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, ProjectsToRebuild = [], } : new() @@ -148,127 +157,203 @@ public Data Dehydrate() /// Returns projects that need to be rebuilt and/or restarted due to blocking rude edits in order to apply changes. /// /// Identifies projects that have been launched. - /// Running projects that have to be restarted. - /// Projects whose source have been updated and need to be rebuilt. + /// + /// Running projects that have to be restarted and a list of projects with rude edits that caused the restart. + /// + /// + /// Projects whose source have been updated and need to be rebuilt. Does not include projects without change that depend on such projects. + /// It is assumed that the host automatically rebuilds all such projects that need rebuilding because it detects the dependent project outputs have been updated. + /// Unordered set. + /// internal static void GetProjectsToRebuildAndRestart( Solution solution, ModuleUpdates moduleUpdates, - IEnumerable rudeEdits, - IImmutableSet runningProjects, - out ImmutableArray projectsToRestart, + ArrayBuilder rudeEdits, + ImmutableDictionary runningProjects, + out ImmutableDictionary> projectsToRestart, out ImmutableArray projectsToRebuild) { + Debug.Assert(!rudeEdits.HasDuplicates(d => d.ProjectId)); + Debug.Assert(rudeEdits.Select(re => re.ProjectId).IsSorted()); + + // Projects with blocking rude edits should not have updates: + Debug.Assert(rudeEdits + .Where(r => r.Diagnostics.HasBlockingRudeEdits()) + .Select(r => r.ProjectId) + .Intersect(moduleUpdates.Updates.Select(u => u.ProjectId)) + .IsEmpty()); + var graph = solution.GetProjectDependencyGraph(); - // First, find all running projects that transitively depend on projects with rude edits. + // First, find all running projects that transitively depend on projects with blocking rude edits + // or edits that have no effect until restart. Note that the latter only trigger restart + // of projects that are configured to restart on no-effect change. + // // These will need to be rebuilt and restarted. In order to rebuilt these projects - // all their transitive references must either be free of source changes or be rebuilt as well. + // all their transitive references must either be free of source changes [*] or be rebuilt as well. // This may add more running projects to the set of projects we need to restart. // We need to repeat this process until we find a fixed point. + // + // [*] If a running project depended on a project with changes and was not restarted, + // the debugger might stop at the changed method body and its current source code + // wouldn't match the IL being executed. - using var _1 = ArrayBuilder.GetInstance(out var traversalStack); - using var _2 = PooledHashSet.GetInstance(out var projectsToRestartBuilder); - using var _3 = ArrayBuilder.GetInstance(out var projectsToRebuildBuilder); + using var _1 = ArrayBuilder.GetInstance(out var traversalStack); - foreach (var projectWithRudeEdit in GetProjectsContainingBlockingRudeEdits(solution)) + // Maps project to restart to all projects with rude edits that caused the restart: + var projectsToRestartBuilder = PooledDictionary>.GetInstance(); + + using var _3 = PooledHashSet.GetInstance(out var projectsToRebuildBuilder); + using var _4 = ArrayBuilder<(ProjectId projectWithRudeEdits, ImmutableArray impactedRunningProjects)>.GetInstance(out var impactedRunningProjectMap); + using var _5 = ArrayBuilder.GetInstance(out var impactedRunningProjects); + + for (var i = 0; i < rudeEdits.Count; i++) { - if (AddImpactedRunningProjects(projectsToRestartBuilder, projectWithRudeEdit)) + var (projectId, projectDiagnostics) = rudeEdits[i]; + + var hasBlocking = projectDiagnostics.HasBlockingRudeEdits(); + var hasNoEffect = projectDiagnostics.HasNoEffectRudeEdits(); + if (!hasBlocking && !hasNoEffect) + { + continue; + } + + AddImpactedRunningProjects(impactedRunningProjects, projectId, hasBlocking); + + foreach (var impactedRunningProject in impactedRunningProjects) { - projectsToRebuildBuilder.Add(projectWithRudeEdit.Id); + projectsToRestartBuilder.MultiAdd(impactedRunningProject, projectId); } + + if (hasBlocking && impactedRunningProjects is []) + { + // Projects with rude edits that do not impact running projects has to be rebuilt, + // so that the change takes effect if it is loaded in future. + projectsToRebuildBuilder.Add(projectId); + } + + impactedRunningProjects.Clear(); } - // At this point the restart set contains all running projects directly affected by rude edits. + // 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. - if (moduleUpdates.Updates.IsEmpty || projectsToRestartBuilder.Count == 0) + // Remove once https://github.com/dotnet/roslyn/issues/78244 is implemented. + if (!runningProjects.Any(static p => p.Value.AllowPartialUpdate)) { - projectsToRestart = [.. projectsToRestartBuilder]; - projectsToRebuild = [.. projectsToRebuildBuilder]; - return; - } - - // The set of updated projects is usually much smaller then the number of all projects in the solution. - // We iterate over this set updating the reset set until no new project is added to the reset set. - // Once a project is determined to affect a running process, all running processes that - // reference this project are added to the reset set. The project is then removed from updated - // project set as it can't contribute any more running projects to the reset set. - // If an updated project does not affect reset set in a given iteration, it stays in the set - // because it may affect reset set later on, after another running project is added to it. + // Partial solution update not supported. + if (projectsToRestartBuilder.Any()) + { + foreach (var update in moduleUpdates.Updates) + { + AddImpactedRunningProjects(impactedRunningProjects, update.ProjectId, isBlocking: true); - using var _4 = PooledHashSet.GetInstance(out var updatedProjects); - using var _5 = ArrayBuilder.GetInstance(out var updatedProjectsToRemove); + foreach (var impactedRunningProject in impactedRunningProjects) + { + projectsToRestartBuilder.TryAdd(impactedRunningProject, []); + } - foreach (var update in moduleUpdates.Updates) - { - updatedProjects.Add(solution.GetRequiredProject(update.ProjectId)); + impactedRunningProjects.Clear(); + } + } } + else if (!moduleUpdates.Updates.IsEmpty && projectsToRestartBuilder.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 reset set until no new project is added to the reset set. + // Once a project is determined to affect a running process, all running processes that + // reference this project are added to the reset set. The project is then removed from updated + // project set as it can't contribute any more running projects to the reset set. + // If an updated project does not affect reset set in a given iteration, it stays in the set + // because it may affect reset set later on, after another running project is added to it. - using var _6 = ArrayBuilder.GetInstance(out var impactedProjects); + using var _6 = PooledHashSet.GetInstance(out var updatedProjects); + using var _7 = ArrayBuilder.GetInstance(out var updatedProjectsToRemove); + using var _8 = PooledHashSet.GetInstance(out var projectsThatCausedRestart); - while (true) - { - Debug.Assert(updatedProjectsToRemove.Count == 0); + updatedProjects.AddRange(moduleUpdates.Updates.Select(static u => u.ProjectId)); - foreach (var updatedProject in updatedProjects) + while (true) { - if (AddImpactedRunningProjects(impactedProjects, updatedProject) && - impactedProjects.Any(projectsToRestartBuilder.Contains)) + Debug.Assert(updatedProjectsToRemove.IsEmpty); + + foreach (var updatedProjectId in updatedProjects) { - projectsToRestartBuilder.AddRange(impactedProjects); - updatedProjectsToRemove.Add(updatedProject); - projectsToRebuildBuilder.Add(updatedProject.Id); + AddImpactedRunningProjects(impactedRunningProjects, updatedProjectId, isBlocking: true); + + Debug.Assert(projectsThatCausedRestart.Count == 0); + + // collect all projects that caused restart of any of the impacted running projects: + foreach (var impactedRunningProject in impactedRunningProjects) + { + if (projectsToRestartBuilder.TryGetValue(impactedRunningProject, out var causes)) + { + projectsThatCausedRestart.AddRange(causes); + } + } + + if (projectsThatCausedRestart.Any()) + { + // The projects that caused the impacted running project to be restarted + // indirectly cause the running project that depends on the updated project to be restarted. + foreach (var impactedRunningProject in impactedRunningProjects) + { + if (!projectsToRestartBuilder.ContainsKey(impactedRunningProject)) + { + projectsToRestartBuilder.MultiAddRange(impactedRunningProject, projectsThatCausedRestart); + } + } + + updatedProjectsToRemove.Add(updatedProjectId); + } + + impactedRunningProjects.Clear(); + projectsThatCausedRestart.Clear(); } - impactedProjects.Clear(); - } + if (updatedProjectsToRemove is []) + { + // none of the remaining updated projects affect restart set: + break; + } - if (updatedProjectsToRemove is []) - { - // none of the remaining updated projects affect restart set: - break; + updatedProjects.RemoveAll(updatedProjectsToRemove); + updatedProjectsToRemove.Clear(); } + } - updatedProjects.RemoveAll(updatedProjectsToRemove); - updatedProjectsToRemove.Clear(); + foreach (var (_, causes) in projectsToRestartBuilder) + { + causes.SortAndRemoveDuplicates(); } - projectsToRestart = [.. projectsToRestartBuilder]; + projectsToRebuildBuilder.AddRange(projectsToRestartBuilder.Keys); + projectsToRestart = projectsToRestartBuilder.ToImmutableMultiDictionaryAndFree(); projectsToRebuild = [.. projectsToRebuildBuilder]; return; - bool AddImpactedRunningProjects(ICollection impactedProjects, Project initialProject) + void AddImpactedRunningProjects(ArrayBuilder impactedProjects, ProjectId initialProject, bool isBlocking) { + Debug.Assert(impactedProjects.IsEmpty); + Debug.Assert(traversalStack.Count == 0); traversalStack.Push(initialProject); - var added = false; - while (traversalStack.Count > 0) { - var project = traversalStack.Pop(); - if (runningProjects.Contains(project.Id)) + var projectId = traversalStack.Pop(); + if (runningProjects.TryGetValue(projectId, out var runningProject) && + (isBlocking || runningProject.RestartWhenChangesHaveNoEffect)) { - impactedProjects.Add(project.Id); - added = true; + impactedProjects.Add(projectId); } - foreach (var referencingProjectId in graph.GetProjectsThatDirectlyDependOnThisProject(project.Id)) + foreach (var referencingProjectId in graph.GetProjectsThatDirectlyDependOnThisProject(projectId)) { - traversalStack.Push(solution.GetRequiredProject(referencingProjectId)); + traversalStack.Push(referencingProjectId); } } - - return added; } - - IEnumerable GetProjectsContainingBlockingRudeEdits(Solution solution) - => rudeEdits - .Where(static e => e.Diagnostics.HasBlockingRudeEdits()) - .Select(static e => e.ProjectId) - .Distinct() - .OrderBy(static id => id) - .Select(solution.GetRequiredProject); } public ImmutableArray GetAllDiagnostics() @@ -295,4 +380,23 @@ public ImmutableArray GetAllDiagnostics() return diagnostics.ToImmutableAndClear(); } + + public ImmutableArray GetAllCompilationDiagnostics() + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + + // add semantic and lowering diagnostics reported during delta emit: + foreach (var (_, projectEmitDiagnostics) in Diagnostics) + { + diagnostics.AddRange(projectEmitDiagnostics); + } + + // add syntax error: + if (SyntaxError != null) + { + diagnostics.Add(SyntaxError); + } + + return diagnostics.ToImmutableAndClear(); + } } diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs index abbbf80f06865..77384c0fc39fe 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs @@ -19,7 +19,7 @@ internal interface IEditAndContinueWorkspaceService : IWorkspaceService internal interface IEditAndContinueService { ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); - ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, IImmutableSet 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); diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs index 7b60868698964..806734d41b877 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs @@ -27,7 +27,7 @@ internal interface ICallback } ValueTask> GetDocumentDiagnosticsAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); - ValueTask EmitSolutionUpdateAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, IImmutableSet 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. diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs index 8fa893a7e06f0..6ec4de320e5e9 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs @@ -56,7 +56,7 @@ await client.TryInvokeAsync( public async ValueTask EmitSolutionUpdateAsync( Solution solution, - IImmutableSet runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { @@ -81,7 +81,7 @@ await client.TryInvokeAsync( RudeEdits = [], SyntaxError = null, ProjectsToRebuild = [], - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, }; } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) @@ -93,7 +93,7 @@ await client.TryInvokeAsync( RudeEdits = [], SyntaxError = null, ProjectsToRebuild = [], - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, }; } @@ -101,7 +101,7 @@ ImmutableArray GetInternalErrorDiagnosticData(string message) { var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(RudeEditKind.InternalError); - var firstProject = solution.GetProject(runningProjects.FirstOrDefault()) ?? solution.Projects.First(); + var firstProject = solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(); var diagnostic = Diagnostic.Create( descriptor, Location.None, diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs index 9b3d43d4488f4..6178b9344d51f 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs @@ -56,9 +56,15 @@ internal static bool IsBlocking(this RudeEditKind kind) internal static bool IsBlockingRudeEdit(this Diagnostic diagnostic) => diagnostic.Descriptor.DefaultSeverity == DiagnosticSeverity.Error; + internal static bool IsNoEffectRudeEdit(this Diagnostic diagnostic) + => EditAndContinueDiagnosticDescriptors.GetRudeEditKind(diagnostic.Id) == RudeEditKind.UpdateMightNotHaveAnyEffect; + public static bool HasBlockingRudeEdits(this ImmutableArray diagnostics) => diagnostics.Any(IsBlockingRudeEdit); - public static bool HasBlockingRudeEdits(this IEnumerable diagnostics) + public static bool HasNoEffectRudeEdits(this ImmutableArray diagnostics) + => diagnostics.Any(IsNoEffectRudeEdit); + + public static bool HasBlockingRudeEdits(this ImmutableArray diagnostics) => diagnostics.Any(static e => e.Kind.IsBlocking()); } diff --git a/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs b/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs new file mode 100644 index 0000000000000..deafbf97e3e06 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs @@ -0,0 +1,24 @@ +// 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/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index dc3c06712f871..f8b8e4b97aa2b 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -95,7 +95,7 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca Contract.ThrowIfFalse(sessionId != default, "Session has not started"); var results = await _encService - .EmitSolutionUpdateAsync(sessionId, solution, runningProjects: [], s_solutionActiveStatementSpanProvider, cancellationToken) + .EmitSolutionUpdateAsync(sessionId, solution, runningProjects: ImmutableDictionary.Empty, s_solutionActiveStatementSpanProvider, cancellationToken) .ConfigureAwait(false); if (results.ModuleUpdates.Status == ModuleUpdateStatus.Ready) diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 2985b5b576ddc..330331bf71a6b 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -64,6 +64,12 @@ internal Update( } } + public readonly struct RunningProjectInfo + { + public required bool RestartWhenChangesHaveNoEffect { get; init; } + } + + [Obsolete("Use Updates2")] public readonly struct Updates( ModuleUpdateStatus status, ImmutableArray diagnostics, @@ -110,6 +116,62 @@ public readonly struct Updates( public ImmutableArray ProjectIdsToRebuild { get; } = projectsToRebuild.SelectAsArray(p => p.Id); } + public enum Status + { + /// + /// No significant changes made that need to be applied. + /// + NoChangesToApply, + + /// + /// Changes can be applied either via updates or restart. + /// + ReadyToApply, + + /// + /// 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. + /// + Blocked, + } + + public readonly struct Updates2 + { + /// + /// Status of the updates. + /// + public readonly Status Status { get; init; } + + /// + /// Syntactic, semantic and emit diagnostics. + /// + /// + /// is if these diagnostics contain any errors. + /// + public required ImmutableArray CompilationDiagnostics { get; init; } + + /// + /// Rude edits per project. + /// + public required ImmutableArray<(ProjectId project, ImmutableArray diagnostics)> RudeEdits { get; init; } + + /// + /// Updates to be applied to modules. Empty if there are blocking rude edits. + /// Only updates to projects that are not included in are listed. + /// + public ImmutableArray ProjectUpdates { get; init; } + + /// + /// Running projects that need to be restarted due to rude edits in order to apply changes. + /// + public ImmutableDictionary> ProjectsToRestart { get; init; } + + /// + /// Projects with changes that need to be rebuilt in order to apply changes. + /// + public ImmutableArray ProjectsToRebuild { get; init; } + } + private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); @@ -163,32 +225,24 @@ public void CapabilitiesChanged() _encService.BreakStateOrCapabilitiesChanged(GetDebuggingSession(), inBreakState: null); } - [Obsolete] - public async Task<(ImmutableArray updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync(Solution solution, CancellationToken cancellationToken) - { - var result = await GetUpdatesAsync(solution, isRunningProject: static _ => false, cancellationToken).ConfigureAwait(false); - return (result.ProjectUpdates, result.Diagnostics); - } - - [Obsolete] - public Task GetUpdatesAsync(Solution solution, Func isRunningProject, CancellationToken cancellationToken) - => GetUpdatesAsync(solution, solution.Projects.Where(isRunningProject).Select(static p => p.Id).ToImmutableHashSet(), cancellationToken); - /// - /// 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. + /// Returns TFM of a given project. /// - /// Solution snapshot. - /// Identifies projects that launched a process. - /// Cancellation token. - /// - /// Updates (one for each changed project) and Rude Edit diagnostics. Does not include syntax or semantic diagnostics. - /// + public static string? GetTargetFramework(Project project) + => project.State.NameAndFlavor.flavor; + + [Obsolete] public async Task GetUpdatesAsync(Solution solution, IImmutableSet runningProjects, CancellationToken cancellationToken) { var sessionId = GetDebuggingSession(); - var results = await _encService.EmitSolutionUpdateAsync(sessionId, solution, runningProjects, s_solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); + var runningProjectsImpl = runningProjects.ToImmutableDictionary(keySelector: p => p, elementSelector: _ => new EditAndContinue.RunningProjectInfo() + { + RestartWhenChangesHaveNoEffect = false, + AllowPartialUpdate = false + }); + + 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. @@ -202,7 +256,7 @@ public async Task GetUpdatesAsync(Solution solution, IImmutableSet + /// 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. + /// + /// Solution snapshot. + /// Identifies projects that launched a process. + /// Cancellation token. + /// + /// Updates (one for each changed project) and Rude Edit diagnostics. Does not include syntax or semantic diagnostics. + /// May include both updates and Rude Edits for different projects. + /// + public async Task GetUpdatesAsync(Solution solution, ImmutableDictionary runningProjects, CancellationToken cancellationToken) + { + var sessionId = GetDebuggingSession(); + + var runningProjectsImpl = runningProjects.ToImmutableDictionary( + static e => e.Key, + static e => new EditAndContinue.RunningProjectInfo() + { + RestartWhenChangesHaveNoEffect = e.Value.RestartWhenChangesHaveNoEffect, + AllowPartialUpdate = true + }); + + 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 (!results.ModuleUpdates.Updates.IsEmpty) + { + _encService.CommitSolutionUpdate(sessionId); + } + + return new Updates2 + { + Status = results.ModuleUpdates.Status switch + { + ModuleUpdateStatus.None => Status.NoChangesToApply, + ModuleUpdateStatus.Ready or ModuleUpdateStatus.RestartRequired => Status.ReadyToApply, + ModuleUpdateStatus.Blocked => Status.Blocked, + _ => throw ExceptionUtilities.UnexpectedValue(results.ModuleUpdates.Status) + }, + CompilationDiagnostics = results.GetAllCompilationDiagnostics(), + RudeEdits = results.RudeEdits.SelectAsArray(static re => (re.ProjectId, re.Diagnostics)), + ProjectUpdates = results.ModuleUpdates.Updates.SelectAsArray(static update => new Update( + update.Module, + update.ProjectId, + update.ILDelta, + update.MetadataDelta, + update.PdbDelta, + update.UpdatedTypes, + update.RequiredCapabilities)), + ProjectsToRestart = results.ProjectsToRestart, + ProjectsToRebuild = results.ProjectsToRebuild + }; + } + public void UpdateBaselines(Solution solution, ImmutableArray projectIds) { var sessionId = GetDebuggingSession(); diff --git a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 150df10079f24..bcc11ab01cf40 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -82,7 +82,7 @@ public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocume using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]); solution = solution - .AddTestProject("P", LanguageNames.CSharp, projectPId).Solution + .AddTestProject("P", LanguageNames.CSharp, id: projectPId).Solution .WithProjectChecksumAlgorithm(projectPId, SourceHashAlgorithm.Sha1); var documentIdA = DocumentId.CreateNewId(projectPId, debugName: "A"); @@ -189,7 +189,7 @@ public async Task ProjectNotBuilt() Assert.Empty(updates.Updates); AssertEx.Equal( [ - $"proj.csproj: (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)}" + $"{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)); EndDebuggingSession(debuggingSession); @@ -404,7 +404,6 @@ public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames. solution = solution. AddTestProject("test", language, out var projectId). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddTestDocument(source, path: sourceFilePath, out var documentId).Project.Solution; var designTimeOnlyDocumentId = DocumentId.CreateNewId(projectId); @@ -525,7 +524,7 @@ public async Task ErrorReadingModuleFile(bool breakMode) var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); Assert.Empty(updates.Updates); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], InspectDiagnostics(emitDiagnostics)); // correct the error: EmitLibrary(projectId, source2); @@ -604,7 +603,7 @@ public async Task ErrorReadingPdbFile() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); EndDebuggingSession(debuggingSession); @@ -626,7 +625,6 @@ public async Task ErrorReadingSourceFile() var document1 = solution. AddTestProject("test"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)). AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8, SourceHashAlgorithm.Sha1), filePath: sourceFile.Path); var project = document1.Project; @@ -651,7 +649,7 @@ public async Task ErrorReadingSourceFile() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); Assert.Empty(updates.Updates); - AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); fileLock.Dispose(); @@ -684,7 +682,6 @@ public async Task FileAdded(bool breakMode) var documentA = solution. AddTestProject("test"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", CreateText(sourceA), filePath: sourceFileA.Path); var project = documentA.Project; @@ -1368,13 +1365,14 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) EmitSolutionUpdateResults result; var readers = ImmutableArray.Empty; + var runningProjects = ImmutableDictionary.Empty.Add(projectId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = true }); // change the source (valid edit): if (validChangeBeforeRudeEdit) { solution = solution.WithDocumentText(documentId, CreateText(source2)); - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: [projectId], s_noActiveSpans, CancellationToken.None); + result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); Assert.Equal(ModuleUpdateStatus.Ready, result.ModuleUpdates.Status); Assert.Empty(result.ProjectsToRebuild); Assert.Empty(result.ProjectsToRestart); @@ -1403,10 +1401,10 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); // validate solution update status and emit: - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: [projectId], s_noActiveSpans, CancellationToken.None); + result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); Assert.Equal(ModuleUpdateStatus.RestartRequired, result.ModuleUpdates.Status); AssertEx.Equal([projectId], result.ProjectsToRebuild); - AssertEx.Equal([projectId], result.ProjectsToRestart); + AssertEx.Equal([projectId], result.ProjectsToRestart.Keys); // restart and rebuild: _debuggerService.LoadedModules.Remove(moduleId); @@ -1433,7 +1431,7 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) Assert.Empty(await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None)); // apply valid change: - result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: [projectId], s_noActiveSpans, CancellationToken.None); + result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); Assert.Equal(ModuleUpdateStatus.Ready, result.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -1561,12 +1559,12 @@ public async Task HasChanges() var projectCId = ProjectId.CreateNewId("C"); solution = solution. - AddTestProject("A", projectAId). + AddTestProject("A", id: projectAId). AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: pathA).Project.Solution. - AddTestProject("B", projectBId). + AddTestProject("B", id: projectBId). AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project. AddDocument("B.cs", "class B {}", filePath: pathB).Project.Solution. - AddTestProject("C", projectCId). + AddTestProject("C", id: projectCId). AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project. AddDocument("C.cs", "class C {}", filePath: pathC).Project.Solution; @@ -1918,7 +1916,7 @@ public async Task Project_Add() // add project that matches assembly B and update the document: var documentB2 = solution. - AddTestProject("B", projectBId). + AddTestProject("B", id: projectBId). AddTestDocument(sourceB2, path: sourceFileB.Path); solution = documentB2.Project.Solution; @@ -2138,7 +2136,7 @@ public async Task Capabilities_SynthesizedNewType() // They are reported as emit diagnostics var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); // no emitted delta: Assert.Empty(updates.Updates); @@ -2227,7 +2225,6 @@ public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDo // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): var document1 = solution. AddTestProject("test"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)). AddDocument("test.cs", CreateText("class C1 { void M() { System.Console.WriteLine(0); } }"), filePath: sourceFile.Path); var documentId = document1.Id; @@ -2315,7 +2312,6 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build): var document2 = solution. AddTestProject("test"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument("test.cs", CreateText(source2), filePath: sourceFile.Path); var documentId = document2.Id; @@ -2361,7 +2357,7 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.Equal( [ - $"test.csproj: (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)}" + $"{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)); // the content actually hasn't changed: @@ -2785,6 +2781,187 @@ partial class C { int Y = 2; } EndDebuggingSession(debuggingSession); } + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78244")] + public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit() + { + var sourceA1 = """ + using System; + + class A + { + static void F() + { + Console.WriteLine(1); + } + } + """; + + var sourceB1 = """ + using System; + + interface I + { + } + """; + + var sourceA2 = """ + using System; + class A + { + static void F() + { + Console.WriteLine(2); + } + } + """; + + var sourceB2 = """ + using System; + + interface I + { + void F() {} + } + """; + + using var _ = CreateWorkspace(out var solution, out var service); + + solution = solution + .AddTestProject("A", out var projectAId) + .AddTestDocument(sourceA1, "A.cs", out var documentAId).Project.Solution + .AddTestProject("B", out var projectBId) + .AddTestDocument(sourceB1, "B.cs", out var documentBId).Project.Solution; + + EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentAId)); + EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentBId)); + + var debuggingSession = await StartDebuggingSessionAsync(service, solution); + + // change the source (valid edit in A and rude edit in B): + solution = solution + .WithDocumentText(documentAId, CreateText(sourceA2)) + .WithDocumentText(documentBId, CreateText(sourceB2)); + + // Rude Edit reported: + var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentBId), s_noActiveSpans, CancellationToken.None); + AssertEx.Equal( + ["ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)], + diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + + // validate solution update status and emit: + var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(emitDiagnostics); + Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); + + // TODO: https://github.com/dotnet/roslyn/issues/78244 + // Should emit delta for the valid change + + //// check emitted delta: + //var delta = updates.Updates.Single(); + //Assert.Empty(delta.ActiveStatements); + //Assert.NotEmpty(delta.ILDelta); + //Assert.NotEmpty(delta.MetadataDelta); + //Assert.NotEmpty(delta.PdbDelta); + //Assert.Equal(6, delta.UpdatedMethods.Length); // F, C.C(), D.D(), E.E(int), E.E(int, int), lambda + //AssertEx.SetEqual([0x02000002, 0x02000003, 0x02000004, 0x02000005], delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); + + //debuggingSession.DiscardSolutionUpdate(); + EndDebuggingSession(debuggingSession); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/78244")] + public async Task MultiProjectUpdates_ValidSignificantChange_NoEffectEdit() + { + var sourceA1 = """ + class A + { + static void F() + { + System.Console.WriteLine(1); + } + } + """; + + var sourceB1 = """ + class B + { + static B() + { + System.Console.WriteLine(10); + } + } + """; + + var sourceA2 = """ + class A + { + static void F() + { + System.Console.WriteLine(2); + } + } + """; + + var sourceB2 = """ + class B + { + static B() + { + System.Console.WriteLine(20); + } + } + """; + + using var _ = CreateWorkspace(out var solution, out var service); + + solution = solution + .AddTestProject("A", out var projectAId) + .AddTestDocument(sourceA1, "A.cs", out var documentAId).Project.Solution + .AddTestProject("B", out var projectBId) + .AddTestDocument(sourceB1, "B.cs", out var documentBId).Project.Solution; + + EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentAId)); + EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentBId)); + + var debuggingSession = await StartDebuggingSessionAsync(service, solution); + + // change the source (valid edit in A and no-effect edit in B): + solution = solution + .WithDocumentText(documentAId, CreateText(sourceA2)) + .WithDocumentText(documentBId, CreateText(sourceB2)); + + // no-effect warning reported: + var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentBId), s_noActiveSpans, CancellationToken.None); + AssertEx.Equal( + ["ENC0118: Warning: " + string.Format(FeaturesResources.Changing_0_might_not_have_any_effect_until_the_application_is_restarted, FeaturesResources.static_constructor)], + diagnostics.Select(d => $"{d.Id}: {d.Severity}: {d.GetMessage()}")); + + // 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 }); + + // emit updates: + var result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); + + AssertEx.SetEqual([], result.ProjectsToRestart.Select(p => p.Key.DebugName)); + + var updates = result.ModuleUpdates; + Assert.Empty(result.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + + // check emitted delta: + Assert.Equal(2, updates.Updates.Length); + + // Process will be restarted, so discard all updates: + debuggingSession.DiscardSolutionUpdate(); + + EndDebuggingSession(debuggingSession); + } + [Theory] [CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/72331")] @@ -2883,7 +3060,7 @@ class C { int Y => 2; } solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2)); // validate solution update status and emit: - var results = (await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: [], s_noActiveSpans, CancellationToken.None).ConfigureAwait(false)).Dehydrate(); + var results = (await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None).ConfigureAwait(false)).Dehydrate(); var diagnostics = results.GetAllDiagnostics(); var generatedFilePath = Path.Combine( @@ -3212,7 +3389,7 @@ public async Task RudeEdit() // They are reported as emit diagnostics var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); // no emitted delta: Assert.Empty(updates.Updates); @@ -3252,7 +3429,6 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() var projectA = documentA.Project; var projectB = solution.AddTestProject("B").WithAssemblyName("A"). - AddMetadataReferences(projectA.MetadataReferences). AddDocument("DocB", source1, filePath: Path.Combine(TempRoot.Root, "DocB.cs")).Project; solution = projectB.Solution; @@ -3393,12 +3569,10 @@ public async Task MultiTargetedPartiallyBuiltProjects() var sourcePath = dir.CreateFile("Lib.cs").WriteAllText(source1, Encoding.UTF8).Path; - var documentA = solution.AddTestProject("A").WithAssemblyName("A"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.NetStandard20)). + var documentA = solution.AddTestProject("A", targetFramework: TargetFramework.NetStandard20).WithAssemblyName("A"). AddDocument("Lib.cs", source1, filePath: sourcePath); - var documentB = documentA.Project.Solution.AddTestProject("B").WithAssemblyName("A"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Net90)). + var documentB = documentA.Project.Solution.AddTestProject("B", targetFramework: TargetFramework.Net90).WithAssemblyName("A"). AddDocument("Lib.cs", source1, filePath: sourcePath); solution = documentB.Project.Solution; @@ -3492,12 +3666,10 @@ public async Task MultiTargeted_AllTargetsStale() var sourcePath = dir.CreateFile("Lib.cs").WriteAllText(source1, Encoding.UTF8).Path; - var documentA = solution.AddTestProject("A").WithAssemblyName("A"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.NetStandard20)). + var documentA = solution.AddTestProject("A", targetFramework: TargetFramework.NetStandard20).WithAssemblyName("A"). AddDocument("Lib.cs", source1, filePath: sourcePath); - var documentB = documentA.Project.Solution.AddTestProject("B").WithAssemblyName("A"). - AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Net90)). + var documentB = documentA.Project.Solution.AddTestProject("B", targetFramework: TargetFramework.Net90).WithAssemblyName("A"). AddDocument("Lib.cs", source1, filePath: sourcePath); solution = documentB.Project.Solution; @@ -3524,8 +3696,8 @@ public async Task MultiTargeted_AllTargetsStale() Assert.Equal(ModuleUpdateStatus.None, updates.Status); AssertEx.Equal( [ - $"A.csproj: (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.csproj: (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)}" + $"{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)); EndDebuggingSession(debuggingSession); @@ -3552,7 +3724,7 @@ public async Task ValidSignificantChange_BaselineCreationFailed_NoStream() solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{document1.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], InspectDiagnostics(emitDiagnostics)); Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); } @@ -3585,7 +3757,7 @@ public async Task ValidSignificantChange_BaselineCreationFailed_AssemblyReadErro solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"{document.Project.FilePath}: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], InspectDiagnostics(emitDiagnostics)); Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status); EndDebuggingSession(debuggingSession); @@ -4570,9 +4742,7 @@ public async Task MultiSession() using var _ = CreateWorkspace(out var solution, out var encService); - var projectP = solution. - AddTestProject("P"). - WithMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)); + var projectP = solution.AddTestProject("P"); solution = projectP.Solution; @@ -4598,14 +4768,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: [], 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: [], 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); @@ -4628,7 +4798,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: [], 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()); diff --git a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs index 7140022638f38..db72d32fd5a77 100644 --- a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs +++ b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs @@ -11,10 +11,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests; @@ -44,17 +46,22 @@ private static ManagedHotReloadUpdate CreateMockUpdate(ProjectId projectId) activeStatements: [], exceptionRegions: []); - private static EmitSolutionUpdateResults CreateMockResults(Solution solution, IEnumerable updates, IEnumerable rudeEdits) - => new() - { - Solution = solution, - ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Blocked, [.. updates.Select(CreateMockUpdate)]), - RudeEdits = [.. rudeEdits.Select(id => new ProjectDiagnostics(id, [Diagnostic.Create(EditAndContinueDiagnosticDescriptors.GetDescriptor(RudeEditKind.InternalError), location: null)]))], - Diagnostics = [], - SyntaxError = null, - ProjectsToRebuild = [], - ProjectsToRestart = [], - }; + private static ModuleUpdates CreateValidUpdates(params IEnumerable projectIds) + => new(ModuleUpdateStatus.Blocked, [.. projectIds.Select(CreateMockUpdate)]); + + private static ArrayBuilder CreateProjectRudeEdits(IEnumerable blocking, IEnumerable noEffect) + => [.. blocking.Select(id => (id, kind: RudeEditKind.InternalError)).Concat(noEffect.Select(id => (id, kind: RudeEditKind.UpdateMightNotHaveAnyEffect))) + .GroupBy(e => e.id) + .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 IEnumerable Inspect(ImmutableDictionary> projectsToRestart) + => projectsToRestart + .OrderBy(kvp => kvp.Key.DebugName) + .Select(kvp => $"{kvp.Key.DebugName}: [{string.Join(",", kvp.Value.Select(id => id.DebugName).Order())}]"); [Fact] public async Task GetHotReloadDiagnostics() @@ -137,7 +144,7 @@ public async Task GetHotReloadDiagnostics() SyntaxError = syntaxError, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Blocked, Updates: []), ProjectsToRebuild = [], - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, }; var actual = data.GetAllDiagnostics(); @@ -163,14 +170,11 @@ public void RunningProjects_Updates() .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; - var runningProjects = new[] { a, b }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: [c, d], rudeEdits: []); - EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(c, d), + CreateProjectRudeEdits(blocking: [], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); @@ -179,7 +183,7 @@ public void RunningProjects_Updates() } [Fact] - public void RunningProjects_RudeEdits() + public void RunningProjects_RudeEdits_SingleImpactedRunningProject() { using var _ = CreateWorkspace(out var solution); @@ -189,22 +193,48 @@ public void RunningProjects_RudeEdits() .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; - var runningProjects = new[] { a, b }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: [], rudeEdits: [d]); - EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(), + CreateProjectRudeEdits(blocking: [d], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); // D has rude edit ==> B has to restart - AssertEx.SetEqual([b], projectsToRestart); + AssertEx.Equal(["B: [D]"], Inspect(projectsToRestart)); - // D has rude edit: - AssertEx.SetEqual([d], projectsToRebuild); + AssertEx.SetEqual([b], projectsToRebuild); + } + + [Fact] + public void RunningProjects_RudeEdits_MultipleImpactedRunningProjects() + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(), + CreateProjectRudeEdits(blocking: [c], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: true), (b, noEffectRestarts: false)]), + out var projectsToRestart, + out var projectsToRebuild); + + // C has rude edit + // ==> A, B have to restart: + AssertEx.Equal( + [ + "A: [C]", + "B: [C]", + ], Inspect(projectsToRestart)); + + AssertEx.SetEqual([a, b], projectsToRebuild); } [Fact] @@ -218,23 +248,106 @@ public void RunningProjects_RudeEdits_NotImpactingRunningProjects() .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; - var runningProjects = new[] { a }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: [], rudeEdits: [d]); + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(), + CreateProjectRudeEdits(blocking: [d], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: false)]), + out var projectsToRestart, + out var projectsToRebuild); + + Assert.Empty(projectsToRestart); + + // Rude edit in projects that doesn't affect running project still causes the updated project to be rebuilt, + // so that the change takes effect if it is loaded in future. + AssertEx.SetEqual([d], projectsToRebuild); + } + + [Fact] + public void RunningProjects_NoEffectEdits_NoEffectRestarts() + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(c), + CreateProjectRudeEdits(blocking: [], noEffect: [c]), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)]), + out var projectsToRestart, + out var projectsToRebuild); + + // C has no-effect edit + // B restarts on no effect changes + // A restarts on blocking changes + // ==> B has to restart + // ==> A has to restart as well since B is restarting and C has an update + AssertEx.Equal( + [ + "A: [C]", + "B: [C]", + ], Inspect(projectsToRestart)); + + AssertEx.SetEqual([a, b], projectsToRebuild); + } + + [Fact] + public void RunningProjects_NoEffectEdits_BlockingRestartsOnly() + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(c), + CreateProjectRudeEdits(blocking: [], noEffect: [c]), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); + // C has no-effect edit + // B restarts on blocking changes + // A restarts on blocking changes + // ==> no restarts/rebuild Assert.Empty(projectsToRestart); Assert.Empty(projectsToRebuild); } [Fact] - public void RunningProjects_RudeEditAndUpdate_Dependent() + public void RunningProjects_NoEffectEdits_NoImpactedRunningProject() + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(d), + CreateProjectRudeEdits(blocking: [], noEffect: [d]), + CreateRunningProjects([(a, noEffectRestarts: false)]), + out var projectsToRestart, + out var projectsToRebuild); + + Assert.Empty(projectsToRestart); + Assert.Empty(projectsToRebuild); + } + + [Fact] + public void RunningProjects_NoEffectEditAndRudeEdit_SameProject() { using var _ = CreateWorkspace(out var solution); @@ -244,27 +357,110 @@ public void RunningProjects_RudeEditAndUpdate_Dependent() .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; - var runningProjects = new[] { a, b }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: [c], rudeEdits: [d]); + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(), + CreateProjectRudeEdits(blocking: [c], noEffect: [c]), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), + out var projectsToRestart, + out var projectsToRebuild); + + // C has rude edit + // ==> A, B have to restart + AssertEx.Equal( + [ + "A: [C]", + "B: [C]", + ], Inspect(projectsToRestart)); + + AssertEx.SetEqual([a, b], projectsToRebuild); + } + + [Theory] + [CombinatorialData] + public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allowPartialUpdate) + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("Q", out var q).Solution + .AddTestProject("P0", out var p0).AddProjectReferences([new(q)]).Solution + .AddTestProject("P1", out var p1).AddProjectReferences([new(q)]).Solution + .AddTestProject("P2", out var p2).Solution + .AddTestProject("R0", out var r0).AddProjectReferences([new(p0)]).Solution + .AddTestProject("R1", out var r1).AddProjectReferences([new(p1), new(p0)]).Solution + .AddTestProject("R2", out var r2).AddProjectReferences([new(p2), new(p0)]).Solution; + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(p0, q), + CreateProjectRudeEdits(blocking: [p1, p2], noEffect: [q]), + CreateRunningProjects([(r0, noEffectRestarts: false), (r1, noEffectRestarts: false), (r2, noEffectRestarts: false)], allowPartialUpdate), + out var projectsToRestart, + out var projectsToRebuild); + + // P1, P2 have rude edits + // ==> R1, R2 have to restart + // P0 has no-effect edit, but R0, R1, R2 do not restart on no-effect edits + // P0 has update, R1 -> P0, R2 -> P0, R1 and R2 are restarting due to rude edits in P1 and P2 + // ==> 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.SetEqual([r0, r1, r2], projectsToRebuild); + } + + [Fact] + public void RunningProjects_RudeEditAndUpdate_Dependent() + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(c), + CreateProjectRudeEdits(blocking: [d], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); // D has rude edit => B has to restart // C has update, B -> C and A -> C ==> A has to restart - AssertEx.SetEqual([a, b], projectsToRestart); + AssertEx.Equal( + [ + "A: [D]", + "B: [D]", + ], Inspect(projectsToRestart)); - // D has rude edit, C has update that impacts restart set: - AssertEx.SetEqual([c, d], projectsToRebuild); + AssertEx.SetEqual([a, b], projectsToRebuild); } - [Fact] - public void RunningProjects_RudeEditAndUpdate_Independent() + [Theory] + [CombinatorialData] + public void RunningProjects_RudeEditAndUpdate_Independent(bool allowPartialUpdate) { using var _ = CreateWorkspace(out var solution); @@ -274,22 +470,74 @@ public void RunningProjects_RudeEditAndUpdate_Independent() .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution .AddTestProject("B", out var b).AddProjectReferences([new(d)]).Solution; - var runningProjects = new[] { a, b }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: [c], rudeEdits: [d]); - EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(c), + CreateProjectRudeEdits(blocking: [d], noEffect: []), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)], allowPartialUpdate), out var projectsToRestart, out var projectsToRebuild); - // D has rude edit => B has to restart - AssertEx.SetEqual([b], projectsToRestart); + 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, C has update that does not impacts restart set: - AssertEx.SetEqual([d], projectsToRebuild); + [Theory] + [CombinatorialData] + public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate) + { + using var _ = CreateWorkspace(out var solution); + + solution = solution + .AddTestProject("C", out var c).Solution + .AddTestProject("D", out var d).Solution + .AddTestProject("A", out var a).AddProjectReferences([new(c)]).Solution + .AddTestProject("B", out var b).AddProjectReferences([new(c), new(d)]).Solution; + + EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( + solution, + CreateValidUpdates(c, d), + CreateProjectRudeEdits(blocking: [], noEffect: [d]), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)], allowPartialUpdate), + out var projectsToRestart, + out var projectsToRebuild); + + // D has no-effect edit + // ==> 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.SetEqual([a, b], projectsToRebuild); } [Theory] @@ -308,18 +556,22 @@ public void RunningProjects_RudeEditAndUpdate_Chain(bool reverse) .AddTestProject("R3", out var r3).AddProjectReferences([new(p3), new(p4)]).Solution .AddTestProject("R4", out var r4).AddProjectReferences([new(p4)]).Solution; - var runningProjects = new[] { r1, r2, r3, r4 }.ToImmutableHashSet(); - var results = CreateMockResults(solution, updates: reverse ? [p4, p3, p2] : [p2, p3, p4], rudeEdits: [p1]); - EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart( solution, - results.ModuleUpdates, - results.RudeEdits, - runningProjects, + CreateValidUpdates(reverse ? [p4, p3, p2] : [p2, p3, p4]), + CreateProjectRudeEdits(blocking: [p1], noEffect: []), + CreateRunningProjects([(r1, noEffectRestarts: false), (r2, noEffectRestarts: false), (r3, noEffectRestarts: false), (r4, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); - AssertEx.SetEqual([r1, r2, r3, r4], projectsToRestart); - AssertEx.SetEqual([p1, p2, p3, p4], projectsToRebuild); + AssertEx.Equal( + [ + "R1: [P1]", + "R2: [P1]", + "R3: [P1]", + "R4: [P1]", + ], Inspect(projectsToRestart)); + + AssertEx.SetEqual([r1, r2, r3, r4], projectsToRebuild); } } diff --git a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 4117e4fe454de..8c84333be83a5 100644 --- a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -3,6 +3,7 @@ // 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.Text; @@ -172,12 +173,16 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); + var runningProjects1 = new Dictionary + { + { project.Id, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = true, AllowPartialUpdate = true} } + }.ToImmutableDictionary(); + mockEncService.EmitSolutionUpdateImpl = (solution, runningProjects, activeStatementSpanProvider) => { var project = solution.GetRequiredProject(projectId); Assert.Equal("proj", project.Name); - AssertEx.Equal(activeSpans1, activeStatementSpanProvider(documentId, "test.cs", CancellationToken.None).AsTask().Result); - AssertEx.Equal([project.Id], runningProjects); + AssertEx.SetEqual(runningProjects1, runningProjects); var deltas = ImmutableArray.Create(new ManagedHotReloadUpdate( module: moduleId1, @@ -211,11 +216,11 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution RudeEdits = [], SyntaxError = syntaxError, ProjectsToRebuild = [project.Id], - ProjectsToRestart = [project.Id], + ProjectsToRestart = ImmutableDictionary>.Empty.Add(project.Id, []), }; }; - var results = await sessionProxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, runningProjects: [project.Id], activeStatementSpanProvider, CancellationToken.None); + var results = await sessionProxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, runningProjects1, activeStatementSpanProvider, CancellationToken.None); AssertEx.Equal($"[{projectId}] Error ENC1001: test.cs(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error")}", Inspect(results.SyntaxError!)); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); diff --git a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs index e3feb6a928f5d..322572403d401 100644 --- a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs +++ b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs @@ -46,14 +46,13 @@ public async Task Test() using var workspace = CreateWorkspace(out var solution, out var encService); var projectP = solution. - AddTestProject("P"). - WithMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)); + AddTestProject("P", out var projectId); solution = projectP.Solution; var moduleId = EmitLibrary(projectP.Id, source1, sourceFileA.Path, assemblyName: "Proj"); - var documentIdA = DocumentId.CreateNewId(projectP.Id, debugName: "A"); + var documentIdA = DocumentId.CreateNewId(projectId, debugName: "A"); solution = solution.AddDocument(DocumentInfo.Create( id: documentIdA, name: "A", @@ -75,19 +74,19 @@ public async Task Test() // Valid update: solution = solution.WithDocumentText(documentIdA, CreateText(source2)); - var result = await hotReload.GetUpdatesAsync(solution, runningProjects: [], CancellationToken.None); - Assert.Empty(result.Diagnostics); + var result = await hotReload.GetUpdatesAsync(solution, runningProjects: ImmutableDictionary.Empty, CancellationToken.None); + Assert.Empty(result.CompilationDiagnostics); Assert.Equal(1, result.ProjectUpdates.Length); AssertEx.Equal([0x02000002], result.ProjectUpdates[0].UpdatedTypes); // Insignificant change: solution = solution.WithDocumentText(documentIdA, CreateText(source3)); - result = await hotReload.GetUpdatesAsync(solution, runningProjects: [], CancellationToken.None); - Assert.Empty(result.Diagnostics); - Assert.Empty(result.Diagnostics); + result = await hotReload.GetUpdatesAsync(solution, runningProjects: ImmutableDictionary.Empty, CancellationToken.None); + Assert.Empty(result.CompilationDiagnostics); + Assert.Empty(result.CompilationDiagnostics); Assert.Empty(result.ProjectUpdates); - Assert.Equal(ModuleUpdateStatus.None, result.Status); + Assert.Equal(WatchHotReloadService.Status.NoChangesToApply, result.Status); var updatedText = await ((EditAndContinueService)hotReload.GetTestAccessor().EncService) .GetTestAccessor() @@ -103,35 +102,38 @@ public async Task Test() // Rude edit: solution = solution.WithDocumentText(documentIdA, CreateText(source4)); - result = await hotReload.GetUpdatesAsync(solution, runningProjects: solution.ProjectIds.ToImmutableHashSet(), CancellationToken.None); + var runningProjects = ImmutableDictionary.Empty + .Add(projectId, new WatchHotReloadService.RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false }); + + result = await hotReload.GetUpdatesAsync(solution, runningProjects, CancellationToken.None); AssertEx.Equal( - ["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], - result.Diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + ["P: ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)], + result.RudeEdits.SelectMany(re => re.diagnostics.Select(d => $"{re.project.DebugName}: {d.Id}: {d.GetMessage()}"))); Assert.Empty(result.ProjectUpdates); - AssertEx.SetEqual(["P"], result.ProjectIdsToRestart.Select(p => solution.GetRequiredProject(p).Name)); - AssertEx.SetEqual(["P"], result.ProjectIdsToRebuild.Select(p => solution.GetRequiredProject(p).Name)); + AssertEx.SetEqual(["P"], result.ProjectsToRestart.Select(p => solution.GetRequiredProject(p.Key).Name)); + AssertEx.SetEqual(["P"], result.ProjectsToRebuild.Select(p => solution.GetRequiredProject(p).Name)); // Syntax error: solution = solution.WithDocumentText(documentIdA, CreateText(source5)); - result = await hotReload.GetUpdatesAsync(solution, runningProjects: solution.ProjectIds.ToImmutableHashSet(), CancellationToken.None); + result = await hotReload.GetUpdatesAsync(solution, runningProjects, CancellationToken.None); AssertEx.Equal( ["CS1002: " + CSharpResources.ERR_SemicolonExpected], - result.Diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + result.CompilationDiagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); Assert.Empty(result.ProjectUpdates); - Assert.Empty(result.ProjectIdsToRestart); - Assert.Empty(result.ProjectIdsToRebuild); + Assert.Empty(result.ProjectsToRestart); + Assert.Empty(result.ProjectsToRebuild); // Semantic error: solution = solution.WithDocumentText(documentIdA, CreateText(source6)); - result = await hotReload.GetUpdatesAsync(solution, runningProjects: solution.ProjectIds.ToImmutableHashSet(), CancellationToken.None); + result = await hotReload.GetUpdatesAsync(solution, runningProjects, CancellationToken.None); AssertEx.Equal( ["CS0103: " + string.Format(CSharpResources.ERR_NameNotInContext, "Unknown")], - result.Diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); + result.CompilationDiagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); Assert.Empty(result.ProjectUpdates); - Assert.Empty(result.ProjectIdsToRestart); - Assert.Empty(result.ProjectIdsToRebuild); + Assert.Empty(result.ProjectsToRestart); + Assert.Empty(result.ProjectsToRebuild); hotReload.EndSession(); } @@ -183,8 +185,11 @@ public async Task SourceGeneratorFailure() solution = solution.WithAdditionalDocumentText(aId, CreateText("updated text")); - var result = await hotReload.GetUpdatesAsync(solution, runningProjects: solution.ProjectIds.ToImmutableHashSet(), CancellationToken.None); - var diagnostic = result.Diagnostics.Single(); + var runningProjects = ImmutableDictionary.Empty + .Add(projectId, new WatchHotReloadService.RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false }); + + var result = await hotReload.GetUpdatesAsync(solution, runningProjects, CancellationToken.None); + var diagnostic = result.CompilationDiagnostics.Single(); Assert.Equal("CS8785", diagnostic.Id); Assert.Contains("Source generator failed", diagnostic.GetMessage()); hotReload.EndSession(); diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs index 2bddd42177e9a..d80c80b20628c 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs @@ -40,7 +40,7 @@ public abstract class EditAndContinueWorkspaceTestBase : TestBase, IDisposable private protected static readonly ActiveStatementSpanProvider s_noActiveSpans = (_, _, _) => new([]); - private protected const TargetFramework DefaultTargetFramework = TargetFramework.NetStandard20; + private protected const TargetFramework DefaultTargetFramework = TargetFramework.NetLatest; private protected readonly Dictionary _mockCompilationOutputs = []; private protected readonly List _telemetryLog = []; @@ -224,7 +224,7 @@ internal static void EndDebuggingSession(DebuggingSession session) Solution solution, ActiveStatementSpanProvider? activeStatementSpanProvider = null) { - var result = await session.EmitSolutionUpdateAsync(solution, runningProjects: [], activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); + var result = await session.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); return (result.ModuleUpdates, result.Diagnostics.OrderBy(d => d.ProjectId.DebugName).ToImmutableArray().ToDiagnosticData(solution)); } diff --git a/src/Features/TestUtilities/EditAndContinue/Extensions.cs b/src/Features/TestUtilities/EditAndContinue/Extensions.cs index d4606b98a0c90..52d6b6b3882b1 100644 --- a/src/Features/TestUtilities/EditAndContinue/Extensions.cs +++ b/src/Features/TestUtilities/EditAndContinue/Extensions.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; using Microsoft.CodeAnalysis.VisualBasic; +using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -52,22 +53,29 @@ public static IEnumerable ToLines(this string str) } } #nullable enable - public static Project AddTestProject(this Solution solution, string projectName, string language = LanguageNames.CSharp) - => AddTestProject(solution, projectName, language, out _); - - public static Project AddTestProject(this Solution solution, string projectName, ProjectId id) - => AddTestProject(solution, projectName, LanguageNames.CSharp, id); public static Project AddTestProject(this Solution solution, string projectName, out ProjectId id) => AddTestProject(solution, projectName, LanguageNames.CSharp, out id); public static Project AddTestProject(this Solution solution, string projectName, string language, out ProjectId id) - => AddTestProject(solution, projectName, language, id = ProjectId.CreateNewId(debugName: projectName)); + => AddTestProject(solution, projectName, language, TargetFramework.NetLatest, id = ProjectId.CreateNewId(debugName: projectName)); - public static Project AddTestProject(this Solution solution, string projectName, string language, ProjectId id) + public static Project AddTestProject(this Solution solution, string projectName, string language, TargetFramework targetFramework, out ProjectId id) { + var project = AddTestProject(solution, projectName, language, targetFramework, id: null); + id = project.Id; + return project; + } + + public static Project AddTestProject(this Solution solution, string projectName, string language = LanguageNames.CSharp, TargetFramework targetFramework = TargetFramework.NetLatest, ProjectId? id = null) + { + id ??= ProjectId.CreateNewId(debugName: projectName); + var info = CreateProjectInfo(projectName, id, language); - return solution.AddProject(info).GetRequiredProject(id); + return solution + .AddProject(info) + .WithProjectMetadataReferences(id, TargetFrameworkUtil.GetReferences(targetFramework)) + .GetRequiredProject(id); } public static Document AddTestDocument(this Project project, string source, string path) @@ -81,7 +89,9 @@ public static Document AddTestDocument(this Solution solution, ProjectId project id = DocumentId.CreateNewId(projectId), name: PathUtilities.GetFileName(path), SourceText.From(source, Encoding.UTF8, SourceHashAlgorithms.Default), - filePath: path).GetRequiredDocument(id); + filePath: PathUtilities.IsAbsolute(path) + ? path : Path.Combine(Path.GetDirectoryName(solution.GetRequiredProject(projectId).FilePath!)!, path)) + .GetRequiredDocument(id); public static Guid CreateProjectTelemetryId(string projectName) { @@ -104,13 +114,13 @@ public static ProjectInfo CreateProjectInfo(string projectName, ProjectId id, st _ => throw ExceptionUtilities.UnexpectedValue(language) }, compilationOptions: TestOptions.DebugDll, - filePath: projectName + language switch + filePath: Path.Combine(TempRoot.Root, projectName, projectName + language switch { LanguageNames.CSharp => ".csproj", LanguageNames.VisualBasic => ".vbproj", NoCompilationConstants.LanguageName => ".noproj", _ => throw ExceptionUtilities.UnexpectedValue(language) - }) + })) .WithCompilationOutputInfo(new CompilationOutputInfo( assemblyPath: Path.Combine(TempRoot.Root, projectName + ".dll"), generatedFilesOutputDirectory: null)) diff --git a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs index e0e5d2e4b28cd..4a90b5ecf172f 100644 --- a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs +++ b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs @@ -23,7 +23,7 @@ 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; @@ -43,7 +43,7 @@ public void DiscardSolutionUpdate(DebuggingSessionId sessionId) public void UpdateBaselines(DebuggingSessionId sessionId, Solution solution, ImmutableArray rebuiltProjects) => UpdateBaselinesImpl?.Invoke(solution, rebuiltProjects); - public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, IImmutableSet 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/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs index 8a86caf87ff33..57b6da1ce129a 100644 --- a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -287,13 +287,17 @@ public async ValueTask GetUpdatesAsync(ImmutableArray.GetInstance(out var runningProjectPaths); runningProjectPaths.AddAll(runningProjects); - var runningProjectIds = solution.Projects.Where(p => p.FilePath != null && runningProjectPaths.Contains(p.FilePath)).Select(static p => p.Id).ToImmutableHashSet(); + + // 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 }); EmitSolutionUpdateResults.Data results; try { - results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectIds, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); + results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectInfos, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -304,7 +308,7 @@ public async ValueTask GetUpdatesAsync(ImmutableArray GetUpdatesAsync(ImmutableArray>.Empty, }; } diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 0c4dc3e39a026..e6172ce24965e 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -142,13 +142,12 @@ public ValueTask> GetDocumentDiagnosticsAsync(Che /// Remote API. /// public ValueTask EmitSolutionUpdateAsync( - Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, IImmutableSet runningProjects, CancellationToken cancellationToken) + Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => { var service = GetService(); - var firstProject = solution.GetProject(runningProjects.FirstOrDefault()) ?? solution.Projects.First(); try { return (await service.EmitSolutionUpdateAsync(sessionId, solution, runningProjects, CreateActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false)).Dehydrate(); @@ -158,11 +157,11 @@ public ValueTask> GetDocumentDiagnosticsAsync(Che return new EmitSolutionUpdateResults.Data() { ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Blocked, []), - Diagnostics = GetUnexpectedUpdateError(firstProject, e.Message), + Diagnostics = GetUnexpectedUpdateError(solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(), e.Message), RudeEdits = [], SyntaxError = null, ProjectsToRebuild = [], - ProjectsToRestart = [], + ProjectsToRestart = ImmutableDictionary>.Empty, }; } }, cancellationToken); From 0a6e36a0644b544f28de972709b18c962c067888 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 26 Apr 2025 07:20:22 -0700 Subject: [PATCH 067/114] Start moving workspace events to fire on background threads (#78134) This addresses a major portion of https://github.com/dotnet/roslyn/issues/78091 There are still some WorkspaceChanged events that haven't been switched over. This is big enough without doing all those and I'd like to get a ci run on this to see how broken tests are. --- src/Compilers/Test/Core/FX/EventWaiter.cs | 23 +- .../WorkspaceTests_EditorFeatures.cs | 102 +++--- ...lassificationTaggerProvider.TagComputer.cs | 22 +- .../ActiveStatementTrackingService.cs | 16 +- .../Core/Remote/SolutionChecksumUpdater.cs | 17 +- ...DocumentActiveContextChangedEventSource.cs | 11 +- ...sticAnalyzerService_IncrementalAnalyzer.cs | 5 +- src/Features/Lsif/Generator/Program.cs | 2 +- .../LspWorkspaceRegistrationService.cs | 47 +-- ...tractCreateServicesOnTextViewConnection.cs | 10 +- .../MiscellaneousFilesWorkspace.cs | 7 +- ...xtViewWindowVerifierInProcessExtensions.cs | 26 +- .../Framework/WorkspaceChangeWatcher.vb | 17 +- .../Impl/Implementation/XamlProjectService.cs | 13 +- .../Api/UnitTestingWorkspaceExtensions.cs | 25 +- .../Core/Portable/Workspace/Workspace.cs | 67 ++-- .../Portable/Workspace/WorkspaceEventMap.cs | 135 ++++++++ .../Workspace/WorkspaceEventOptions.cs | 11 + .../Workspace/WorkspaceEventRegistration.cs | 30 ++ .../Portable/Workspace/Workspace_Editor.cs | 4 +- .../Portable/Workspace/Workspace_Events.cs | 302 ++++++------------ .../Workspace/Workspace_EventsLegacy.cs | 138 ++++++++ .../Workspace/Workspace_Registration.cs | 9 +- .../WorkspaceTests/AdhocWorkspaceTests.cs | 16 +- .../CoreTestUtilities/WorkspaceExtensions.cs | 2 +- .../MSBuild/Test/MSBuildWorkspaceTestBase.cs | 2 +- .../Core/CompilerExtensions.projitems | 1 - .../Compiler/Core/Utilities/EventMap.cs | 176 ---------- 28 files changed, 658 insertions(+), 578 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/WorkspaceEventMap.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/WorkspaceEventOptions.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/WorkspaceEventRegistration.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/Workspace_EventsLegacy.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EventMap.cs diff --git a/src/Compilers/Test/Core/FX/EventWaiter.cs b/src/Compilers/Test/Core/FX/EventWaiter.cs index 524d7178c2598..145d0d2b7db87 100644 --- a/src/Compilers/Test/Core/FX/EventWaiter.cs +++ b/src/Compilers/Test/Core/FX/EventWaiter.cs @@ -5,11 +5,7 @@ #nullable disable using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace Roslyn.Test.Utilities { @@ -55,6 +51,25 @@ public EventHandler Wrap(EventHandler input) }; } + public Action Wrap(Action input) + { + return args => + { + try + { + input(args); + } + catch (Exception ex) + { + _capturedException = ex; + } + finally + { + _eventSignal.Set(); + } + }; + } + /// /// Use this method to block the test until the operation enclosed in the Wrap method completes /// diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 0a778c32da08d..6c3434d5b8171 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -62,7 +62,7 @@ public async Task TestEmptySolutionUpdateDoesNotFireEvents() var solution = workspace.CurrentSolution; var workspaceChanged = false; - workspace.WorkspaceChanged += (s, e) => workspaceChanged = true; + using var _ = workspace.RegisterWorkspaceChangedHandler(e => workspaceChanged = true); // make an 'empty' update by claiming something changed, but its the same as before workspace.OnParseOptionsChanged(project.Id, project.ParseOptions); @@ -815,15 +815,15 @@ public async Task TestDocumentEvents() using var openWaiter = new EventWaiter(); // Wrapping event handlers so they can notify us on being called. var documentOpenedEventHandler = openWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, + args => Assert.True(args.Document.Id == document.Id, "The document given to the 'DocumentOpened' event handler did not have the same id as the one created for the test.")); var documentClosedEventHandler = closeWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, + args => Assert.True(args.Document.Id == document.Id, "The document given to the 'DocumentClosed' event handler did not have the same id as the one created for the test.")); - workspace.DocumentOpened += documentOpenedEventHandler; - workspace.DocumentClosed += documentClosedEventHandler; + var documentOpenedDisposer = workspace.RegisterDocumentOpenedHandler(documentOpenedEventHandler); + var documentClosedDisposer = workspace.RegisterDocumentClosedHandler(documentClosedEventHandler); workspace.OpenDocument(document.Id); workspace.CloseDocument(document.Id); @@ -833,15 +833,15 @@ public async Task TestDocumentEvents() // Wait to receive signal that events have fired. Assert.True(openWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'DocumentOpened' was not fired within {0} minutes.", + string.Format("event for 'RegisterDocumentOpenedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); Assert.True(closeWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'DocumentClosed' was not fired within {0} minutes.", + string.Format("event for 'RegisterDocumentClosedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); - workspace.DocumentOpened -= documentOpenedEventHandler; - workspace.DocumentClosed -= documentClosedEventHandler; + documentOpenedDisposer.Dispose(); + documentClosedDisposer.Dispose(); workspace.OpenDocument(document.Id); workspace.CloseDocument(document.Id); @@ -852,11 +852,11 @@ public async Task TestDocumentEvents() // Verifying that an event has not been called is difficult to prove. // All events should have already been called so we wait 5 seconds and then assume the event handler was removed correctly. Assert.False(openWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'DocumentOpened' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterDocumentOpenedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); Assert.False(closeWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'DocumentClosed' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterDocumentClosedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); } @@ -881,15 +881,15 @@ public async Task TestSourceGeneratedDocumentEvents() // Wrapping event handlers so they can notify us on being called. var documentOpenedEventHandler = openWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, - $"The source generated document given to the '{nameof(Workspace.DocumentOpened)}' event handler did not have the same id as the one created for the test.")); + args => Assert.True(args.Document.Id == document.Id, + $"The source generated document given to the '{nameof(Workspace.RegisterDocumentOpenedHandler)}' did not have the same id as the one created for the test.")); var documentClosedEventHandler = closeWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, - $"The source generated document given to the '{nameof(Workspace.DocumentClosed)}' event handler did not have the same id as the one created for the test.")); + args => Assert.True(args.Document.Id == document.Id, + $"The source generated document given to the '{nameof(Workspace.RegisterDocumentClosedHandler)}' did not have the same id as the one created for the test.")); - workspace.DocumentOpened += documentOpenedEventHandler; - workspace.DocumentClosed += documentClosedEventHandler; + var documentOpenedDisposer = workspace.RegisterDocumentOpenedHandler(documentOpenedEventHandler); + var documentClosedDisposer = workspace.RegisterDocumentClosedHandler(documentClosedEventHandler); workspace.OpenSourceGeneratedDocument(document.Id); var sourceGeneratedDocumentId = workspace.GetDocumentIdInCurrentContext(document.GetOpenTextContainer()); @@ -902,15 +902,15 @@ public async Task TestSourceGeneratedDocumentEvents() // Wait to receive signal that events have fired. Assert.True(openWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'DocumentOpened' was not fired within {0} minutes.", + string.Format("event for 'RegisterDocumentOpenedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); Assert.True(closeWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'DocumentClosed' was not fired within {0} minutes.", + string.Format("event for 'RegisterDocumentClosedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); - workspace.DocumentOpened -= documentOpenedEventHandler; - workspace.DocumentClosed -= documentClosedEventHandler; + documentOpenedDisposer.Dispose(); + documentClosedDisposer.Dispose(); workspace.OpenSourceGeneratedDocument(document.Id); await workspace.CloseSourceGeneratedDocumentAsync(document.Id); @@ -921,11 +921,11 @@ public async Task TestSourceGeneratedDocumentEvents() // Verifying that an event has not been called is difficult to prove. // All events should have already been called so we wait 5 seconds and then assume the event handler was removed correctly. Assert.False(openWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'DocumentOpened' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterDocumentOpenedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); Assert.False(closeWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'DocumentClosed' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterDocumentClosedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); } @@ -944,16 +944,16 @@ public async Task TestAdditionalDocumentEvents() using var closeWaiter = new EventWaiter(); using var openWaiter = new EventWaiter(); // Wrapping event handlers so they can notify us on being called. - var documentOpenedEventHandler = openWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, - "The document given to the 'AdditionalDocumentOpened' event handler did not have the same id as the one created for the test.")); + var textDocumentOpenedEventHandler = openWaiter.Wrap( + args => Assert.True(args.Document.Id == document.Id, + "The document given to the 'RegisterTextDocumentOpenedHandler' event handler did not have the same id as the one created for the test.")); - var documentClosedEventHandler = closeWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, - "The document given to the 'AdditionalDocumentClosed' event handler did not have the same id as the one created for the test.")); + var textDocumentClosedEventHandler = closeWaiter.Wrap( + args => Assert.True(args.Document.Id == document.Id, + "The document given to the 'RegisterTextDocumentClosedHandler' event handler did not have the same id as the one created for the test.")); - workspace.TextDocumentOpened += documentOpenedEventHandler; - workspace.TextDocumentClosed += documentClosedEventHandler; + var textDocumentOpenedDisposer = workspace.RegisterTextDocumentOpenedHandler(textDocumentOpenedEventHandler); + var textDocumentClosedDisposer = workspace.RegisterTextDocumentClosedHandler(textDocumentClosedEventHandler); workspace.OpenAdditionalDocument(document.Id); workspace.CloseAdditionalDocument(document.Id); @@ -963,15 +963,15 @@ public async Task TestAdditionalDocumentEvents() // Wait to receive signal that events have fired. Assert.True(openWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'AdditionalDocumentOpened' was not fired within {0} minutes.", + string.Format("event for 'RegisterTextDocumentOpenedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); Assert.True(closeWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'AdditionalDocumentClosed' was not fired within {0} minutes.", + string.Format("event for 'RegisterTextDocumentClosedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); - workspace.TextDocumentOpened -= documentOpenedEventHandler; - workspace.TextDocumentClosed -= documentClosedEventHandler; + textDocumentOpenedDisposer.Dispose(); + textDocumentClosedDisposer.Dispose(); workspace.OpenAdditionalDocument(document.Id); workspace.CloseAdditionalDocument(document.Id); @@ -982,11 +982,11 @@ public async Task TestAdditionalDocumentEvents() // Verifying that an event has not been called is difficult to prove. // All events should have already been called so we wait 5 seconds and then assume the event handler was removed correctly. Assert.False(openWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'AdditionalDocumentOpened' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterTextDocumentOpenedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); Assert.False(closeWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'AdditionalDocumentClosed' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterTextDocumentClosedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); } @@ -1005,16 +1005,16 @@ public async Task TestAnalyzerConfigDocumentEvents() using var closeWaiter = new EventWaiter(); using var openWaiter = new EventWaiter(); // Wrapping event handlers so they can notify us on being called. - var documentOpenedEventHandler = openWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, + var textDocumentOpenedEventHandler = openWaiter.Wrap( + args => Assert.True(args.Document.Id == document.Id, "The document given to the 'AnalyzerConfigDocumentOpened' event handler did not have the same id as the one created for the test.")); - var documentClosedEventHandler = closeWaiter.Wrap( - (sender, args) => Assert.True(args.Document.Id == document.Id, + var textDocumentClosedEventHandler = closeWaiter.Wrap( + args => Assert.True(args.Document.Id == document.Id, "The document given to the 'AnalyzerConfigDocumentClosed' event handler did not have the same id as the one created for the test.")); - workspace.TextDocumentOpened += documentOpenedEventHandler; - workspace.TextDocumentClosed += documentClosedEventHandler; + var textDocumentOpenedDisposer = workspace.RegisterTextDocumentOpenedHandler(textDocumentOpenedEventHandler); + var textDocumentClosedDisposer = workspace.RegisterTextDocumentClosedHandler(textDocumentClosedEventHandler); workspace.OpenAnalyzerConfigDocument(document.Id); workspace.CloseAnalyzerConfigDocument(document.Id); @@ -1024,15 +1024,15 @@ public async Task TestAnalyzerConfigDocumentEvents() // Wait to receive signal that events have fired. Assert.True(openWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'AnalyzerConfigDocumentOpened' was not fired within {0} minutes.", + string.Format("event for 'RegisterTextDocumentOpenedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); Assert.True(closeWaiter.WaitForEventToFire(longEventTimeout), - string.Format("event 'AnalyzerConfigDocumentClosed' was not fired within {0} minutes.", + string.Format("event for 'RegisterTextDocumentClosedHandler' was not fired within {0} minutes.", longEventTimeout.Minutes)); - workspace.TextDocumentOpened -= documentOpenedEventHandler; - workspace.TextDocumentClosed -= documentClosedEventHandler; + textDocumentOpenedDisposer.Dispose(); + textDocumentClosedDisposer.Dispose(); workspace.OpenAnalyzerConfigDocument(document.Id); workspace.CloseAnalyzerConfigDocument(document.Id); @@ -1043,11 +1043,11 @@ public async Task TestAnalyzerConfigDocumentEvents() // Verifying that an event has not been called is difficult to prove. // All events should have already been called so we wait 5 seconds and then assume the event handler was removed correctly. Assert.False(openWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'AnalyzerConfigDocumentOpened' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterTextDocumentOpenedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); Assert.False(closeWaiter.WaitForEventToFire(shortEventTimeout), - string.Format("event handler 'AnalyzerConfigDocumentClosed' was called within {0} seconds though it was removed from the list.", + string.Format("event for 'RegisterTextDocumentClosedHandler' was called within {0} seconds though it was removed from the list.", shortEventTimeout.Seconds)); } @@ -1420,11 +1420,11 @@ public async Task TestLinkedFilesStayInSync() using var workspace = EditorTestWorkspace.Create(input, composition: EditorTestCompositions.EditorFeatures, openDocuments: true); var eventArgs = new List(); - workspace.WorkspaceChanged += (s, e) => + using var _ = workspace.RegisterWorkspaceChangedHandler(e => { Assert.Equal(WorkspaceChangeKind.DocumentChanged, e.Kind); eventArgs.Add(e); - }; + }); var originalDocumentId = workspace.GetOpenDocumentIds().Single(id => !workspace.GetTestDocument(id).IsLinkFile); var linkedDocumentId = workspace.GetOpenDocumentIds().Single(id => workspace.GetTestDocument(id).IsLinkFile); diff --git a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs index 64e62295d6774..c3200dd73944b 100644 --- a/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Classification/Syntactic/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -62,6 +62,8 @@ private sealed record CachedServices( private readonly TimeSpan _diffTimeout; private Workspace? _workspace; + private WorkspaceEventRegistration? _workspaceChangedDisposer; + private WorkspaceEventRegistration? _workspaceDocumentActiveContextChangedDisposer; /// /// Cached values for the last services we computed for a particular and (); - _workspace.DocumentOpened += DocumentOpened; - _workspace.DocumentClosed += DocumentClosed; + _documentOpenedHandlerDisposer = _workspace.RegisterDocumentOpenedHandler(DocumentOpened); + _documentClosedHandlerDisposer = _workspace.RegisterDocumentClosedHandler(DocumentClosed); } internal Dictionary> Test_GetTrackingSpans() @@ -133,8 +135,8 @@ public void EndTracking() _cancellationSource.Cancel(); _cancellationSource.Dispose(); - _workspace.DocumentOpened -= DocumentOpened; - _workspace.DocumentClosed -= DocumentClosed; + _documentOpenedHandlerDisposer.Dispose(); + _documentClosedHandlerDisposer.Dispose(); lock (_trackingSpans) { @@ -142,7 +144,7 @@ public void EndTracking() } } - private void DocumentClosed(object? sender, DocumentEventArgs e) + private void DocumentClosed(DocumentEventArgs e) { if (e.Document.FilePath != null) { @@ -153,7 +155,7 @@ private void DocumentClosed(object? sender, DocumentEventArgs e) } } - private void DocumentOpened(object? sender, DocumentEventArgs e) + private void DocumentOpened(DocumentEventArgs e) => _ = TrackActiveSpansAsync(e.Document); private async Task TrackActiveSpansAsync(Document designTimeDocument) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 762beb5668272..479211f9a3f08 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -43,7 +43,8 @@ internal sealed class SolutionChecksumUpdater private readonly AsyncBatchingWorkQueue _synchronizeActiveDocumentQueue; private readonly object _gate = new(); - private bool _isSynchronizeWorkspacePaused; + private readonly WorkspaceEventRegistration _workspaceChangedDisposer; + private readonly WorkspaceEventRegistration _workspaceChangedImmediateDisposer; private readonly CancellationToken _shutdownToken; @@ -52,6 +53,8 @@ internal sealed class SolutionChecksumUpdater private const string SynchronizeTextChangesStatusSucceededKeyName = nameof(SolutionChecksumUpdater) + "." + SynchronizeTextChangesStatusSucceededMetricName; private const string SynchronizeTextChangesStatusFailedKeyName = nameof(SolutionChecksumUpdater) + "." + SynchronizeTextChangesStatusFailedMetricName; + private bool _isSynchronizeWorkspacePaused; + public SolutionChecksumUpdater( Workspace workspace, IAsynchronousOperationListenerProvider listenerProvider, @@ -79,8 +82,8 @@ public SolutionChecksumUpdater( shutdownToken); // start listening workspace change event - _workspace.WorkspaceChanged += OnWorkspaceChanged; - _workspace.WorkspaceChangedImmediate += OnWorkspaceChangedImmediate; + _workspaceChangedDisposer = _workspace.RegisterWorkspaceChangedHandler(this.OnWorkspaceChanged); + _workspaceChangedImmediateDisposer = _workspace.RegisterWorkspaceChangedImmediateHandler(OnWorkspaceChangedImmediate); _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; if (_globalOperationService != null) @@ -100,8 +103,8 @@ public void Shutdown() PauseSynchronizingPrimaryWorkspace(); _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; - _workspace.WorkspaceChanged -= OnWorkspaceChanged; - _workspace.WorkspaceChangedImmediate -= OnWorkspaceChangedImmediate; + _workspaceChangedDisposer.Dispose(); + _workspaceChangedImmediateDisposer.Dispose(); if (_globalOperationService != null) { @@ -136,7 +139,7 @@ private void ResumeSynchronizingPrimaryWorkspace() } } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs _) { // Check if we're currently paused. If so ignore this notification. We don't want to any work in response // to whatever the workspace is doing. @@ -147,7 +150,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) } } - private void OnWorkspaceChangedImmediate(object? sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChangedImmediate(WorkspaceChangeEventArgs e) { if (e.Kind == WorkspaceChangeKind.DocumentChanged) { diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs index f027bad3102a9..ee02ce7ef6cf7 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.DocumentActiveContextChangedEventSource.cs @@ -11,20 +11,21 @@ internal partial class TaggerEventSources { private sealed class DocumentActiveContextChangedEventSource(ITextBuffer subjectBuffer) : AbstractWorkspaceTrackingTaggerEventSource(subjectBuffer) { + private WorkspaceEventRegistration? _documentActiveContextChangedDisposer; + + // Require main thread on the callback as RaiseChanged implementors may have main thread dependencies. protected override void ConnectToWorkspace(Workspace workspace) - => workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; + => _documentActiveContextChangedDisposer = workspace.RegisterDocumentActiveContextChangedHandler(OnDocumentActiveContextChanged, WorkspaceEventOptions.RequiresMainThreadOptions); protected override void DisconnectFromWorkspace(Workspace workspace) - => workspace.DocumentActiveContextChanged -= OnDocumentActiveContextChanged; + => _documentActiveContextChangedDisposer?.Dispose(); - private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs e) + private void OnDocumentActiveContextChanged(DocumentActiveContextChangedEventArgs e) { var document = SubjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); if (document != null && document.Id == e.NewActiveContextDocumentId) - { this.RaiseChanged(); - } } } } diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs index b9598aacd82f4..74a5ef511712f 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs @@ -14,11 +14,8 @@ private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace worksp private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspace workspace) { // subscribe to active context changed event for new workspace - workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; + _ = workspace.RegisterDocumentActiveContextChangedHandler(args => RequestDiagnosticRefresh()); return new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); } - - private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs e) - => RequestDiagnosticRefresh(); } diff --git a/src/Features/Lsif/Generator/Program.cs b/src/Features/Lsif/Generator/Program.cs index 3284c2673c433..8d32b07de419a 100644 --- a/src/Features/Lsif/Generator/Program.cs +++ b/src/Features/Lsif/Generator/Program.cs @@ -182,7 +182,7 @@ private static async Task GenerateWithMSBuildWorkspaceAsync( var solutionLoadStopwatch = Stopwatch.StartNew(); using var msbuildWorkspace = MSBuildWorkspace.Create(await Composition.CreateHostServicesAsync()); - msbuildWorkspace.WorkspaceFailed += (s, e) => logger.Log(e.Diagnostic.Kind == WorkspaceDiagnosticKind.Failure ? LogLevel.Error : LogLevel.Warning, "Problem while loading: " + e.Diagnostic.Message); + _ = msbuildWorkspace.RegisterWorkspaceFailedHandler((e) => logger.Log(e.Diagnostic.Kind == WorkspaceDiagnosticKind.Failure ? LogLevel.Error : LogLevel.Warning, "Problem while loading: " + e.Diagnostic.Message)); var solution = await openAsync(msbuildWorkspace, cancellationToken); diff --git a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs index 9ed6a051b03d4..1b7a230ddb6b6 100644 --- a/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs +++ b/src/LanguageServer/Protocol/Workspaces/LspWorkspaceRegistrationService.cs @@ -12,7 +12,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer; internal abstract class LspWorkspaceRegistrationService : IDisposable { private readonly object _gate = new(); + + // These arrays are kept in sync, with _workspaceChangedDisposers[i] representing + // a disposer for a WorkspaceChanged event on the workspace at _registrations[i] private ImmutableArray _registrations = []; + private ImmutableArray _workspaceChangedDisposers = []; public ImmutableArray GetAllRegistrations() { @@ -35,13 +39,15 @@ public virtual void Register(Workspace? workspace) m["WorkspacePartialSemanticsEnabled"] = workspace.PartialSemanticsEnabled; }, workspace)); + // Forward workspace change events for all registered LSP workspaces. Requires main thread as it + // fires LspSolutionChanged which hasn't been guaranteed to be thread safe. + var workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnLspWorkspaceChanged, WorkspaceEventOptions.RequiresMainThreadOptions); + lock (_gate) { _registrations = _registrations.Add(workspace); + _workspaceChangedDisposers = _workspaceChangedDisposers.Add(workspaceChangedDisposer); } - - // Forward workspace change events for all registered LSP workspaces. - workspace.WorkspaceChanged += OnLspWorkspaceChanged; } public void Deregister(Workspace? workspace) @@ -49,14 +55,26 @@ public void Deregister(Workspace? workspace) if (workspace is null) return; - workspace.WorkspaceChanged -= OnLspWorkspaceChanged; + WorkspaceEventRegistration? disposer = null; lock (_gate) { - _registrations = _registrations.Remove(workspace); + var index = _registrations.IndexOf(workspace); + + // Handle the case where we were registered with a null workspace, but deregistered + // with a non-null workspace + if (index >= 0) + { + _registrations = _registrations.RemoveAt(index); + + disposer = _workspaceChangedDisposers[index]; + _workspaceChangedDisposers = _workspaceChangedDisposers.RemoveAt(index); + } } + + disposer?.Dispose(); } - private void OnLspWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void OnLspWorkspaceChanged(WorkspaceChangeEventArgs e) { LspSolutionChanged?.Invoke(this, e); } @@ -65,12 +83,13 @@ public void Dispose() { lock (_gate) { - foreach (var workspace in _registrations) + foreach (var disposer in _workspaceChangedDisposers) { - workspace.WorkspaceChanged -= OnLspWorkspaceChanged; + disposer.Dispose(); } - _registrations = _registrations.Clear(); + _registrations = []; + _workspaceChangedDisposers = []; } } @@ -81,13 +100,3 @@ public void Dispose() /// public EventHandler? LspSolutionChanged; } - -internal sealed class LspWorkspaceRegisteredEventArgs : EventArgs -{ - public Workspace Workspace { get; } - - public LspWorkspaceRegisteredEventArgs(Workspace workspace) - { - Workspace = workspace; - } -} diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs index 01e7b4d0faf02..d608cda53e5a9 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractCreateServicesOnTextViewConnection.cs @@ -26,10 +26,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService /// Creates services on the first connection of an applicable subject buffer to an IWpfTextView. /// This ensures the services are available by the time an open document or the interactive window needs them. /// -internal abstract class AbstractCreateServicesOnTextViewConnection : IWpfTextViewConnectionListener +internal abstract class AbstractCreateServicesOnTextViewConnection : IWpfTextViewConnectionListener, IDisposable { private readonly string _languageName; private readonly AsyncBatchingWorkQueue _workQueue; + private readonly WorkspaceEventRegistration _workspaceDocumentOpenedDisposer; private bool _initialized = false; protected VisualStudioWorkspace Workspace { get; } @@ -56,9 +57,12 @@ public AbstractCreateServicesOnTextViewConnection( listenerProvider.GetListener(FeatureAttribute.CompletionSet), threadingContext.DisposalToken); - Workspace.DocumentOpened += QueueWorkOnDocumentOpened; + _workspaceDocumentOpenedDisposer = Workspace.RegisterDocumentOpenedHandler(QueueWorkOnDocumentOpened); } + public void Dispose() + => _workspaceDocumentOpenedDisposer.Dispose(); + void IWpfTextViewConnectionListener.SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) { if (!_initialized) @@ -96,7 +100,7 @@ private async ValueTask BatchProcessProjectsWithOpenedDocumentAsync(ImmutableSeg } } - private void QueueWorkOnDocumentOpened(object sender, DocumentEventArgs e) + private void QueueWorkOnDocumentOpened(DocumentEventArgs e) { if (e.Document.Project.Language == _languageName) _workQueue.AddWork(e.Document.Project.Id); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs index 635b2e643bad0..7e82dc6a8a2a2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -22,6 +22,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.TextManager.Interop; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.WorkspaceEventMap; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -172,7 +173,11 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) // to the RDT in the background thread. Since this is all asynchronous a bit more asynchrony is fine. if (!_threadingContext.JoinableTaskContext.IsOnMainThread) { - ScheduleTask(() => Registration_WorkspaceChanged(sender, e)); + // Require main thread on the callback as this method requires that per the comment above. + var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => Registration_WorkspaceChanged(sender, e), WorkspaceEventOptions.RequiresMainThreadOptions); + var handlerSet = EventHandlerSet.Create(handlerAndOptions); + + ScheduleTask(e, handlerSet); return; } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs index d0a6733054fdf..229cee06bb58a 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs @@ -62,10 +62,9 @@ public static async Task CodeActionAsync( CancellationToken cancellationToken = default) { var events = new List(); - void WorkspaceChangedHandler(object sender, WorkspaceChangeEventArgs e) => events.Add(e); var workspace = await textViewWindowVerifier.TestServices.Shell.GetComponentModelServiceAsync(cancellationToken); - using var workspaceEventRestorer = WithWorkspaceChangedHandler(workspace, WorkspaceChangedHandler); + using var _ = workspace.RegisterWorkspaceChangedHandler(e => events.Add(e)); await textViewWindowVerifier.TestServices.Editor.ShowLightBulbAsync(cancellationToken); @@ -155,12 +154,6 @@ await textViewWindowVerifier.TestServices.Workspace.WaitForAllAsyncOperationsAsy Assert.NotEqual("text", tokenType); } - private static WorkspaceEventRestorer WithWorkspaceChangedHandler(Workspace workspace, EventHandler eventHandler) - { - workspace.WorkspaceChanged += eventHandler; - return new WorkspaceEventRestorer(workspace, eventHandler); - } - private static LoggerRestorer WithLogger(ILogger logger) { return new LoggerRestorer(Logger.SetLogger(logger)); @@ -195,23 +188,6 @@ public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int uniq } } - private readonly struct WorkspaceEventRestorer : IDisposable - { - private readonly Workspace _workspace; - private readonly EventHandler _eventHandler; - - public WorkspaceEventRestorer(Workspace workspace, EventHandler eventHandler) - { - _workspace = workspace; - _eventHandler = eventHandler; - } - - public void Dispose() - { - _workspace.WorkspaceChanged -= _eventHandler; - } - } - private readonly struct LoggerRestorer : IDisposable { private readonly ILogger? _logger; diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/WorkspaceChangeWatcher.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/WorkspaceChangeWatcher.vb index a17f533e73553..00a894fa3f2b6 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/WorkspaceChangeWatcher.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/WorkspaceChangeWatcher.vb @@ -4,26 +4,27 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Shared.TestHooks -Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Friend Class WorkspaceChangeWatcher Implements IDisposable - Private ReadOnly _environment As TestEnvironment Private ReadOnly _asynchronousOperationWaiter As IAsynchronousOperationWaiter + Private ReadOnly _workspaceChangedDisposer As WorkspaceEventRegistration Private _changeEvents As New List(Of WorkspaceChangeEventArgs) Public Sub New(environment As TestEnvironment) - _environment = environment - Dim listenerProvider = environment.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)() _asynchronousOperationWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) - AddHandler environment.Workspace.WorkspaceChanged, AddressOf OnWorkspaceChanged + _workspaceChangedDisposer = environment.Workspace.RegisterWorkspaceChangedHandler(AddressOf OnWorkspaceChanged) + End Sub + + Public Sub Dispose() Implements IDisposable.Dispose + _workspaceChangedDisposer.Dispose() End Sub - Private Sub OnWorkspaceChanged(sender As Object, e As WorkspaceChangeEventArgs) + Private Sub OnWorkspaceChanged(e As WorkspaceChangeEventArgs) _changeEvents.Add(e) End Sub @@ -35,9 +36,5 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr _changeEvents = New List(Of WorkspaceChangeEventArgs)() Return changeEvents End Function - - Public Sub Dispose() Implements IDisposable.Dispose - RemoveHandler _environment.Workspace.WorkspaceChanged, AddressOf OnWorkspaceChanged - End Sub End Class End Namespace diff --git a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs index 7ca45df9a32fc..a8f3afa25c7c7 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs @@ -27,7 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml; [Export] -internal sealed partial class XamlProjectService +internal sealed partial class XamlProjectService : IDisposable { private readonly IServiceProvider _serviceProvider; private readonly Workspace _workspace; @@ -36,6 +36,7 @@ internal sealed partial class XamlProjectService private readonly IThreadingContext _threadingContext; private readonly Dictionary _xamlProjects = []; private readonly ConcurrentDictionary _documentIds = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly WorkspaceEventRegistration _documentClosedHandlerDisposer; private RunningDocumentTable? _rdt; private IVsSolution? _vsSolution; @@ -58,9 +59,13 @@ public XamlProjectService( AnalyzerService = analyzerService; - _workspace.DocumentClosed += OnDocumentClosed; + // Require main thread on the callback as OnDocumentClosed is not thread safe. + _documentClosedHandlerDisposer = _workspace.RegisterDocumentClosedHandler(OnDocumentClosed, WorkspaceEventOptions.RequiresMainThreadOptions); } + public void Dispose() + => _documentClosedHandlerDisposer.Dispose(); + public static IXamlDocumentAnalyzerService? AnalyzerService { get; private set; } public DocumentId? TrackOpenDocument(string filePath) @@ -188,13 +193,11 @@ public XamlProjectService( return null; } - private void OnDocumentClosed(object sender, DocumentEventArgs e) + private void OnDocumentClosed(DocumentEventArgs e) { var filePath = e.Document.FilePath; if (filePath == null) - { return; - } if (_documentIds.TryGetValue(filePath, out var documentId)) { diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingWorkspaceExtensions.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingWorkspaceExtensions.cs index 04560fce22dba..85bf53f439eba 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingWorkspaceExtensions.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingWorkspaceExtensions.cs @@ -16,28 +16,19 @@ public static IDisposable RegisterTextDocumentClosedEventHandler(this Workspace private sealed class EventHandlerWrapper : IDisposable { - private readonly Workspace _workspace; - private readonly EventHandler _handler; - private readonly bool _opened; + private readonly WorkspaceEventRegistration _textDocumentOperationDisposer; internal EventHandlerWrapper(Workspace workspace, Action action, bool opened) { - _workspace = workspace; - _handler = (sender, args) => action(new UnitTestingTextDocumentEventArgsWrapper(args)); - _opened = opened; - - if (_opened) - _workspace.TextDocumentOpened += _handler; - else - _workspace.TextDocumentClosed += _handler; + _textDocumentOperationDisposer = opened + ? workspace.RegisterTextDocumentOpenedHandler(HandleEvent) + : workspace.RegisterTextDocumentClosedHandler(HandleEvent); + + void HandleEvent(TextDocumentEventArgs args) + => action(new UnitTestingTextDocumentEventArgsWrapper(args)); } public void Dispose() - { - if (_opened) - _workspace.TextDocumentOpened -= _handler; - else - _workspace.TextDocumentClosed -= _handler; - } + => _textDocumentOperationDisposer.Dispose(); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index a89523e20d5a3..751c3e213bcdb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -23,6 +23,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.WorkspaceEventMap; namespace Microsoft.CodeAnalysis; @@ -39,7 +40,7 @@ public abstract partial class Workspace : IDisposable private readonly IAsynchronousOperationListener _asyncOperationListener; - private readonly AsyncBatchingWorkQueue _workQueue; + private readonly AsyncBatchingWorkQueue<(EventArgs, EventHandlerSet)> _eventHandlerWorkQueue; private readonly CancellationTokenSource _workQueueTokenSource = new(); private readonly ITaskSchedulerProvider _taskSchedulerProvider; @@ -80,9 +81,9 @@ protected Workspace(HostServices host, string? workspaceKind) var listenerProvider = Services.GetRequiredService(); _asyncOperationListener = listenerProvider.GetListener(); - _workQueue = new( + _eventHandlerWorkQueue = new( TimeSpan.Zero, - ProcessWorkQueueAsync, + ProcessEventHandlerWorkQueueAsync, _asyncOperationListener, _workQueueTokenSource.Token); @@ -592,8 +593,18 @@ internal void UpdateCurrentSolutionOnOptionsChanged() #pragma warning disable IDE0060 // Remove unused parameter protected internal Task ScheduleTask(Action action, string? taskName = "Workspace.Task") { - _workQueue.AddWork(action); - return _workQueue.WaitUntilCurrentBatchCompletesAsync(); + // Require main thread on the callback as this is publicly exposed and the given actions may have main thread dependencies. + var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => action(), WorkspaceEventOptions.RequiresMainThreadOptions); + var handlerSet = EventHandlerSet.Create(handlerAndOptions); + + return ScheduleTask(EventArgs.Empty, handlerSet); + } + + [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] + internal Task ScheduleTask(EventArgs args, EventHandlerSet handlerSet) + { + _eventHandlerWorkQueue.AddWork((args, handlerSet)); + return _eventHandlerWorkQueue.WaitUntilCurrentBatchCompletesAsync(); } /// @@ -603,8 +614,12 @@ protected internal Task ScheduleTask(Action action, string? taskName = "Workspac protected internal async Task ScheduleTask(Func func, string? taskName = "Workspace.Task") { T? result = default; - _workQueue.AddWork(() => result = func()); - await _workQueue.WaitUntilCurrentBatchCompletesAsync().ConfigureAwait(false); + + // Require main thread on the callback as this is publicly exposed and the given actions may have main thread dependencies. + var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => result = func(), WorkspaceEventOptions.RequiresMainThreadOptions); + var handlerSet = EventHandlerSet.Create(handlerAndOptions); + + await ScheduleTask(EventArgs.Empty, handlerSet).ConfigureAwait(false); return result!; } #pragma warning restore IDE0060 // Remove unused parameter @@ -716,25 +731,37 @@ protected virtual void Dispose(bool finalize) _workQueueTokenSource.Cancel(); } - private async ValueTask ProcessWorkQueueAsync(ImmutableSegmentedList list, CancellationToken cancellationToken) + private async ValueTask ProcessEventHandlerWorkQueueAsync(ImmutableSegmentedList<(EventArgs Args, EventHandlerSet HandlerSet)> list, CancellationToken cancellationToken) { - // Hop over to the right scheduler to execute all this work. - await Task.Factory.StartNew(() => + // Currently, this method maintains ordering of events to their callbacks. If that were no longer necessary, + // further performance optimizations of this method could be considered, such as grouping together + // callbacks by eventargs/registries or parallelizing callbacks. + + // ABWQ callbacks occur on threadpool threads, so we can process the handlers immediately that don't require the main thread. + ProcessWorkQueueHelper(list, shouldRaise: static options => !options.RequiresMainThread, cancellationToken); + + // Verify there are handlers which require the main thread before performing the thread switch. + if (list.Any(static x => x.HandlerSet.HasMatchingOptions(isMatch: static options => options.RequiresMainThread))) { - foreach (var item in list) + await Task.Factory.StartNew(() => + { + // As the scheduler has moved us to the main thread, we can now process the handlers that require the main thread. + ProcessWorkQueueHelper(list, shouldRaise: static options => options.RequiresMainThread, cancellationToken); + }, cancellationToken, TaskCreationOptions.None, _taskSchedulerProvider.CurrentContextScheduler).ConfigureAwait(false); + } + + static void ProcessWorkQueueHelper( + ImmutableSegmentedList<(EventArgs Args, EventHandlerSet handlerSet)> list, + Func shouldRaise, + CancellationToken cancellationToken) + { + foreach (var (args, handlerSet) in list) { cancellationToken.ThrowIfCancellationRequested(); - try - { - item(); - } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e)) - { - // Ensure we continue onto further items, even if one particular item fails. - } + handlerSet.RaiseEvent(args, shouldRaise); } - }, cancellationToken, TaskCreationOptions.None, _taskSchedulerProvider.CurrentContextScheduler).ConfigureAwait(false); + } } #region Host API diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceEventMap.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventMap.cs new file mode 100644 index 0000000000000..25c312703ccbc --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventMap.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.ErrorReporting; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Workspace; + +namespace Microsoft.CodeAnalysis; + +internal sealed class WorkspaceEventMap +{ + private readonly object _guard = new(); + private readonly Dictionary _eventTypeToHandlerSet = []; + + public WorkspaceEventRegistration AddEventHandler(WorkspaceEventType eventType, WorkspaceEventHandlerAndOptions handlerAndOptions) + { + lock (_guard) + { + _eventTypeToHandlerSet[eventType] = GetEventHandlerSet_NoLock(eventType).AddHandler(handlerAndOptions); + } + + return new WorkspaceEventRegistration(this, eventType, handlerAndOptions); + } + + public void RemoveEventHandler(WorkspaceEventType eventType, WorkspaceEventHandlerAndOptions handlerAndOptions) + { + lock (_guard) + { + var originalHandlers = GetEventHandlerSet_NoLock(eventType); + var newHandlers = originalHandlers.RemoveHandler(handlerAndOptions); + + // An earlier AddEventHandler call would have created the WorkspaceEventRegistration whose + // disposal would have called this method. + Debug.Assert(originalHandlers != newHandlers); + + _eventTypeToHandlerSet[eventType] = newHandlers; + } + } + + public EventHandlerSet GetEventHandlerSet(WorkspaceEventType eventType) + { + lock (_guard) + { + return GetEventHandlerSet_NoLock(eventType); + } + } + + private EventHandlerSet GetEventHandlerSet_NoLock(WorkspaceEventType eventType) + { + return _eventTypeToHandlerSet.TryGetValue(eventType, out var handlers) + ? handlers + : EventHandlerSet.Empty; + } + + public readonly record struct WorkspaceEventHandlerAndOptions(Action Handler, WorkspaceEventOptions Options); + + public sealed class EventHandlerSet(ImmutableArray registries) + { + public static readonly EventHandlerSet Empty = new([]); + private readonly ImmutableArray _registries = registries; + + public static EventHandlerSet Create(WorkspaceEventHandlerAndOptions handlerAndOptions) + => new EventHandlerSet([new Registry(handlerAndOptions)]); + + public EventHandlerSet AddHandler(WorkspaceEventHandlerAndOptions handlerAndOptions) + => new EventHandlerSet(_registries.Add(new Registry(handlerAndOptions))); + + public EventHandlerSet RemoveHandler(WorkspaceEventHandlerAndOptions handlerAndOptions) + { + var registry = _registries.FirstOrDefault(static (r, handlerAndOptions) => r.HasHandlerAndOptions(handlerAndOptions), handlerAndOptions); + + Debug.Assert(registry != null, "Expected to find a registry for the handler and options."); + if (registry == null) + return this; + + // disable this handler (so pending raise events can be squelched) + // This does not guarantee no race condition between Raise and Remove but greatly reduces it. + registry.Unregister(); + + var newRegistries = _registries.Remove(registry); + + return newRegistries.IsEmpty ? Empty : new(newRegistries); + } + + public bool HasHandlers + => !_registries.IsEmpty; + + public bool HasMatchingOptions(Func isMatch) + => _registries.Any(static (r, hasOptions) => r.HasMatchingOptions(hasOptions), isMatch); + + public void RaiseEvent(TEventArgs arg, Func shouldRaiseEvent) + where TEventArgs : EventArgs + { + foreach (var registry in _registries) + { + try + { + registry.RaiseEvent(arg, shouldRaiseEvent); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + // Ensure we continue onto further items, even if one particular item fails. + } + } + } + } + + public sealed class Registry(WorkspaceEventHandlerAndOptions handlerAndOptions) + { + private readonly WorkspaceEventHandlerAndOptions _handlerAndOptions = handlerAndOptions; + private bool _disableHandler = false; + + public void Unregister() + => _disableHandler = true; + + public bool HasHandlerAndOptions(WorkspaceEventHandlerAndOptions handlerAndOptions) + => _handlerAndOptions.Equals(handlerAndOptions); + + public bool HasMatchingOptions(Func isMatch) + => !_disableHandler && isMatch(_handlerAndOptions.Options); + + public void RaiseEvent(EventArgs args, Func shouldRaiseEvent) + { + if (!_disableHandler && shouldRaiseEvent(_handlerAndOptions.Options)) + _handlerAndOptions.Handler(args); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceEventOptions.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventOptions.cs new file mode 100644 index 0000000000000..522e52fc13a47 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventOptions.cs @@ -0,0 +1,11 @@ +// 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. + +namespace Microsoft.CodeAnalysis; + +internal record struct WorkspaceEventOptions(bool RequiresMainThread) +{ + public static readonly WorkspaceEventOptions DefaultOptions = new(RequiresMainThread: false); + public static readonly WorkspaceEventOptions RequiresMainThreadOptions = new(RequiresMainThread: true); +} diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceEventRegistration.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventRegistration.cs new file mode 100644 index 0000000000000..e54261036c404 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceEventRegistration.cs @@ -0,0 +1,30 @@ +// 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.Threading; +using static Microsoft.CodeAnalysis.Workspace; +using static Microsoft.CodeAnalysis.WorkspaceEventMap; + +namespace Microsoft.CodeAnalysis; + +/// +/// Represents a registration for a workspace event, ensuring the event handler is properly unregistered when disposed. +/// +/// This class is used to manage the lifecycle of an event handler associated with a workspace event. +/// When the instance is disposed, the event handler is automatically unregistered from the event map. +internal sealed class WorkspaceEventRegistration(WorkspaceEventMap eventMap, WorkspaceEventType eventType, WorkspaceEventHandlerAndOptions handlerAndOptions) : IDisposable +{ + private readonly WorkspaceEventType _eventType = eventType; + private readonly WorkspaceEventHandlerAndOptions _handlerAndOptions = handlerAndOptions; + private WorkspaceEventMap? _eventMap = eventMap; + + public void Dispose() + { + // Protect against simultaneous disposal from multiple threads + var eventMap = Interlocked.Exchange(ref _eventMap, null); + + eventMap?.RemoveEventHandler(_eventType, _handlerAndOptions); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index 24bf354de7229..471b32c7bbfa9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -457,7 +457,7 @@ internal void OnSourceGeneratedDocumentOpened( // We raise 2 events for source document opened. var token = _asyncOperationListener.BeginAsyncOperation(nameof(OnSourceGeneratedDocumentOpened)); _ = RaiseDocumentOpenedEventAsync(document).CompletesAsyncOperation(token); - token = _asyncOperationListener.BeginAsyncOperation(TextDocumentOpenedEventName); + token = _asyncOperationListener.BeginAsyncOperation(nameof(WorkspaceEventType.TextDocumentOpened)); _ = RaiseTextDocumentOpenedEventAsync(document).CompletesAsyncOperation(token); } @@ -477,7 +477,7 @@ internal void OnSourceGeneratedDocumentClosed(SourceGeneratedDocument document) // We raise 2 events for source document closed. var token = _asyncOperationListener.BeginAsyncOperation(nameof(OnSourceGeneratedDocumentClosed)); _ = RaiseDocumentClosedEventAsync(document).CompletesAsyncOperation(token); - token = _asyncOperationListener.BeginAsyncOperation(TextDocumentClosedEventName); + token = _asyncOperationListener.BeginAsyncOperation(nameof(WorkspaceEventType.TextDocumentClosed)); _ = RaiseTextDocumentClosedEventAsync(document).CompletesAsyncOperation(token); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs index 38cba793833a0..9ac3bd1527c1c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Events.cs @@ -2,260 +2,162 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.WorkspaceEventMap; namespace Microsoft.CodeAnalysis; public abstract partial class Workspace { - private readonly EventMap _eventMap = new(); + private readonly WorkspaceEventMap _eventMap = new(); + + internal enum WorkspaceEventType + { + DocumentActiveContextChanged, + DocumentClosed, + DocumentOpened, + TextDocumentClosed, + TextDocumentOpened, + WorkspaceChange, + WorkspaceChangedImmediate, + WorkspaceFailed, + } - private const string WorkspaceChangeEventName = "WorkspaceChanged"; - private const string WorkspaceChangedImmediateEventName = "WorkspaceChangedImmediate"; - private const string WorkspaceFailedEventName = "WorkspaceFailed"; - private const string DocumentOpenedEventName = "DocumentOpened"; - private const string DocumentClosedEventName = "DocumentClosed"; - private const string DocumentActiveContextChangedName = "DocumentActiveContextChanged"; - private const string TextDocumentOpenedEventName = "TextDocumentOpened"; - private const string TextDocumentClosedEventName = "TextDocumentClosed"; + private IWorkspaceEventListenerService? _workspaceEventListenerService; - private IWorkspaceEventListenerService _workspaceEventListenerService; + #region Event Registration /// - /// An event raised whenever the current solution is changed. + /// Registers a handler that is fired whenever the current solution is changed. /// - public event EventHandler WorkspaceChanged - { - add - { - _eventMap.AddEventHandler(WorkspaceChangeEventName, value); - } + internal WorkspaceEventRegistration RegisterWorkspaceChangedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.WorkspaceChange, handler, options); - remove - { - _eventMap.RemoveEventHandler(WorkspaceChangeEventName, value); - } - } + /// + /// Registers a handler that is fired *immediately* whenever the current solution is changed. + /// Handlers should be written to be very fast. Always called from the thread changing the workspace, + /// regardless of the preferences indicated by the passed in options. This thread my vary depending + /// on the workspace. + /// + internal WorkspaceEventRegistration RegisterWorkspaceChangedImmediateHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.WorkspaceChangedImmediate, handler, options); + + /// + /// Registers a handler that is fired whenever the workspace or part of its solution model + /// fails to access a file or other external resource. + /// + internal WorkspaceEventRegistration RegisterWorkspaceFailedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.WorkspaceFailed, handler, options); + + /// + /// Registers a handler that is fired when a is opened in the editor. + /// + internal WorkspaceEventRegistration RegisterDocumentOpenedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.DocumentOpened, handler, options); /// - /// An event raised *immediately* whenever the current solution is changed. Handlers - /// should be written to be very fast. Called on the same thread changing the workspace, - /// which may vary depending on the workspace. + /// Registers a handler that is fired when a is closed in the editor. /// - internal event EventHandler WorkspaceChangedImmediate + internal WorkspaceEventRegistration RegisterDocumentClosedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.DocumentClosed, handler, options); + + /// + /// Registers a handler that is fired when any is opened in the editor. + /// + internal WorkspaceEventRegistration RegisterTextDocumentOpenedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.TextDocumentOpened, handler, options); + + /// + /// Registers a handler that is fired when any is closed in the editor. + /// + internal WorkspaceEventRegistration RegisterTextDocumentClosedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.TextDocumentClosed, handler, options); + + /// + /// Registers a handler that is fired when the active context document associated with a buffer + /// changes. + /// + internal WorkspaceEventRegistration RegisterDocumentActiveContextChangedHandler(Action handler, WorkspaceEventOptions? options = null) + => RegisterHandler(WorkspaceEventType.DocumentActiveContextChanged, handler, options); + + private WorkspaceEventRegistration RegisterHandler(WorkspaceEventType eventType, Action handler, WorkspaceEventOptions? options = null) + where TEventArgs : EventArgs { - add - { - _eventMap.AddEventHandler(WorkspaceChangedImmediateEventName, value); - } + var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => handler((TEventArgs)args), options ?? WorkspaceEventOptions.DefaultOptions); - remove - { - _eventMap.RemoveEventHandler(WorkspaceChangedImmediateEventName, value); - } + return _eventMap.AddEventHandler(eventType, handlerAndOptions); } - protected Task RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind kind, Solution oldSolution, Solution newSolution, ProjectId projectId = null, DocumentId documentId = null) + #endregion + + protected Task RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind kind, Solution oldSolution, Solution newSolution, ProjectId? projectId = null, DocumentId? documentId = null) { if (newSolution == null) - { throw new ArgumentNullException(nameof(newSolution)); - } if (oldSolution == newSolution) - { return Task.CompletedTask; - } if (projectId == null && documentId != null) - { projectId = documentId.ProjectId; - } - WorkspaceChangeEventArgs args = null; - var ev = GetEventHandlers(WorkspaceChangedImmediateEventName); + WorkspaceChangeEventArgs? args = null; - if (ev.HasHandlers) + var immediateHandlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceChangedImmediate); + if (immediateHandlerSet.HasHandlers) { args = new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId); - RaiseEventForHandlers(ev, sender: this, args, FunctionId.Workspace_EventsImmediate); + immediateHandlerSet.RaiseEvent(args, shouldRaiseEvent: static option => true); } - ev = GetEventHandlers(WorkspaceChangeEventName); - if (ev.HasHandlers) + var handlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceChange); + if (handlerSet.HasHandlers) { args ??= new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId); - return this.ScheduleTask(() => - { - RaiseEventForHandlers(ev, sender: this, args, FunctionId.Workspace_Events); - }, WorkspaceChangeEventName); - } - else - { - return Task.CompletedTask; + return this.ScheduleTask(args, handlerSet); } - static void RaiseEventForHandlers( - EventMap.EventHandlerSet> handlers, - Workspace sender, - WorkspaceChangeEventArgs args, - FunctionId functionId) - { - using (Logger.LogBlock(functionId, (s, p, d, k) => $"{s.Id} - {p} - {d} {args.Kind.ToString()}", args.NewSolution, args.ProjectId, args.DocumentId, args.Kind, CancellationToken.None)) - { - handlers.RaiseEvent(static (handler, arg) => handler(arg.sender, arg.args), (sender, args)); - } - } - } - - /// - /// An event raised whenever the workspace or part of its solution model - /// fails to access a file or other external resource. - /// - public event EventHandler WorkspaceFailed - { - add - { - _eventMap.AddEventHandler(WorkspaceFailedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(WorkspaceFailedEventName, value); - } + return Task.CompletedTask; } protected internal virtual void OnWorkspaceFailed(WorkspaceDiagnostic diagnostic) { - var ev = GetEventHandlers(WorkspaceFailedEventName); - if (ev.HasHandlers) + var handlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceFailed); + if (handlerSet.HasHandlers) { var args = new WorkspaceDiagnosticEventArgs(diagnostic); - ev.RaiseEvent(static (handler, arg) => handler(arg.self, arg.args), (self: this, args)); - } - } - - /// - /// An event that is fired when a is opened in the editor. - /// - public event EventHandler DocumentOpened - { - add - { - _eventMap.AddEventHandler(DocumentOpenedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(DocumentOpenedEventName, value); + handlerSet.RaiseEvent(args, shouldRaiseEvent: static option => true); } } protected Task RaiseDocumentOpenedEventAsync(Document document) - => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), DocumentOpenedEventName); - - /// - /// An event that is fired when any is opened in the editor. - /// - public event EventHandler TextDocumentOpened - { - add - { - _eventMap.AddEventHandler(TextDocumentOpenedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(TextDocumentOpenedEventName, value); - } - } + => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), WorkspaceEventType.DocumentOpened); protected Task RaiseTextDocumentOpenedEventAsync(TextDocument document) - => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), TextDocumentOpenedEventName); + => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), WorkspaceEventType.TextDocumentOpened); private Task RaiseTextDocumentOpenedOrClosedEventAsync( TDocument document, TDocumentEventArgs args, - string eventName) + WorkspaceEventType eventType) where TDocument : TextDocument where TDocumentEventArgs : EventArgs { - var ev = GetEventHandlers(eventName); - if (ev.HasHandlers && document != null) - { - return this.ScheduleTask(() => - { - ev.RaiseEvent(static (handler, arg) => handler(arg.self, arg.args), (self: this, args)); - }, eventName); - } - else - { - return Task.CompletedTask; - } - } - - /// - /// An event that is fired when a is closed in the editor. - /// - public event EventHandler DocumentClosed - { - add - { - _eventMap.AddEventHandler(DocumentClosedEventName, value); - } + var handlerSet = GetEventHandlers(eventType); + if (handlerSet.HasHandlers && document != null) + return this.ScheduleTask(args, handlerSet); - remove - { - _eventMap.RemoveEventHandler(DocumentClosedEventName, value); - } + return Task.CompletedTask; } protected Task RaiseDocumentClosedEventAsync(Document document) - => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), DocumentClosedEventName); - - /// - /// An event that is fired when any is closed in the editor. - /// - public event EventHandler TextDocumentClosed - { - add - { - _eventMap.AddEventHandler(TextDocumentClosedEventName, value); - } - - remove - { - _eventMap.RemoveEventHandler(TextDocumentClosedEventName, value); - } - } + => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), WorkspaceEventType.DocumentClosed); protected Task RaiseTextDocumentClosedEventAsync(TextDocument document) - => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), TextDocumentClosedEventName); - - /// - /// An event that is fired when the active context document associated with a buffer - /// changes. - /// - public event EventHandler DocumentActiveContextChanged - { - add - { - _eventMap.AddEventHandler(DocumentActiveContextChangedName, value); - } - - remove - { - _eventMap.RemoveEventHandler(DocumentActiveContextChangedName, value); - } - } + => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), WorkspaceEventType.TextDocumentClosed); [Obsolete("This member is obsolete. Use the RaiseDocumentActiveContextChangedEventAsync(SourceTextContainer, DocumentId, DocumentId) overload instead.", error: true)] protected Task RaiseDocumentActiveContextChangedEventAsync(Document document) @@ -263,30 +165,28 @@ protected Task RaiseDocumentActiveContextChangedEventAsync(Document document) protected Task RaiseDocumentActiveContextChangedEventAsync(SourceTextContainer sourceTextContainer, DocumentId oldActiveContextDocumentId, DocumentId newActiveContextDocumentId) { - var ev = GetEventHandlers(DocumentActiveContextChangedName); - if (ev.HasHandlers && sourceTextContainer != null && oldActiveContextDocumentId != null && newActiveContextDocumentId != null) + if (sourceTextContainer == null || oldActiveContextDocumentId == null || newActiveContextDocumentId == null) + return Task.CompletedTask; + + var handlerSet = GetEventHandlers(WorkspaceEventType.DocumentActiveContextChanged); + if (handlerSet.HasHandlers) { // Capture the current solution snapshot (inside the _serializationLock of OnDocumentContextUpdated) var currentSolution = this.CurrentSolution; + var args = new DocumentActiveContextChangedEventArgs(currentSolution, sourceTextContainer, oldActiveContextDocumentId, newActiveContextDocumentId); - return this.ScheduleTask(() => - { - var args = new DocumentActiveContextChangedEventArgs(currentSolution, sourceTextContainer, oldActiveContextDocumentId, newActiveContextDocumentId); - ev.RaiseEvent(static (handler, arg) => handler(arg.self, arg.args), (self: this, args)); - }, "Workspace.WorkspaceChanged"); - } - else - { - return Task.CompletedTask; + return this.ScheduleTask(args, handlerSet); } + + return Task.CompletedTask; } - private EventMap.EventHandlerSet> GetEventHandlers(string eventName) where T : EventArgs + private EventHandlerSet GetEventHandlers(WorkspaceEventType eventType) { // this will register features that want to listen to workspace events // lazily first time workspace event is actually fired EnsureEventListeners(); - return _eventMap.GetEventHandlers>(eventName); + return _eventMap.GetEventHandlerSet(eventType); } private void EnsureEventListeners() diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_EventsLegacy.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_EventsLegacy.cs new file mode 100644 index 0000000000000..58028bd0d7c9a --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_EventsLegacy.cs @@ -0,0 +1,138 @@ +// 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.Diagnostics; + +namespace Microsoft.CodeAnalysis; + +public abstract partial class Workspace +{ + /// + /// Allows conversion of legacy event handlers to the new event system. The first item in + /// the key's tuple is an EventHandler<TEventArgs> and thus stored as an object. + /// + private readonly Dictionary<(object eventHandler, WorkspaceEventType eventType), (int adviseCount, IDisposable disposer)> _disposableEventHandlers = new(); + private readonly object _legacyWorkspaceEventsGate = new(); + + /// + /// An event raised whenever the current solution is changed. + /// + public event EventHandler WorkspaceChanged + { + add => AddLegacyEventHandler(value, WorkspaceEventType.WorkspaceChange); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.WorkspaceChange); + } + + /// + /// An event raised whenever the workspace or part of its solution model + /// fails to access a file or other external resource. + /// + public event EventHandler WorkspaceFailed + { + add => AddLegacyEventHandler(value, WorkspaceEventType.WorkspaceFailed); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.WorkspaceFailed); + } + + /// + /// An event that is fired when a is opened in the editor. + /// + public event EventHandler DocumentOpened + { + add => AddLegacyEventHandler(value, WorkspaceEventType.DocumentOpened); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.DocumentOpened); + } + + /// + /// An event that is fired when any is opened in the editor. + /// + public event EventHandler TextDocumentOpened + { + add => AddLegacyEventHandler(value, WorkspaceEventType.TextDocumentOpened); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.TextDocumentOpened); + } + + /// + /// An event that is fired when a is closed in the editor. + /// + public event EventHandler DocumentClosed + { + add => AddLegacyEventHandler(value, WorkspaceEventType.DocumentClosed); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.DocumentClosed); + } + + /// + /// An event that is fired when any is closed in the editor. + /// + public event EventHandler TextDocumentClosed + { + add => AddLegacyEventHandler(value, WorkspaceEventType.TextDocumentClosed); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.TextDocumentClosed); + } + + /// + /// An event that is fired when the active context document associated with a buffer + /// changes. + /// + public event EventHandler DocumentActiveContextChanged + { + add => AddLegacyEventHandler(value, WorkspaceEventType.DocumentActiveContextChanged); + remove => RemoveLegacyEventHandler(value, WorkspaceEventType.DocumentActiveContextChanged); + } + + private void AddLegacyEventHandler(EventHandler eventHandler, WorkspaceEventType eventType) + where TEventArgs : EventArgs + { + // Require main thread on the callback as this is used from publicly exposed events + // and those callbacks may have main thread dependencies. + var key = (eventHandler, eventType); + + lock (_legacyWorkspaceEventsGate) + { + if (_disposableEventHandlers.TryGetValue(key, out var adviseCountAndDisposer)) + { + // If we already have a handler for this event type, update the map with the new advise count + _disposableEventHandlers[key] = (adviseCountAndDisposer.adviseCount + 1, adviseCountAndDisposer.disposer); + } + else + { + // Safe to call RegisterHandler inside the lock as it doesn't invoke code outside the workspace event map code. + var disposer = RegisterHandler(eventType, (Action)Handler, WorkspaceEventOptions.RequiresMainThreadOptions); + _disposableEventHandlers[key] = (adviseCount: 1, disposer); + } + } + + return; + + void Handler(EventArgs arg) + => eventHandler(sender: this, (TEventArgs)arg); + } + + private void RemoveLegacyEventHandler(EventHandler eventHandler, WorkspaceEventType eventType) + where TEventArgs : EventArgs + { + IDisposable? disposer = null; + + lock (_legacyWorkspaceEventsGate) + { + if (_disposableEventHandlers.TryGetValue((eventHandler, eventType), out var adviseCountAndDisposer)) + { + // If we already have a handler for this event type, just increment the advise count + // and return. + if (adviseCountAndDisposer.adviseCount == 1) + { + disposer = adviseCountAndDisposer.disposer; + _disposableEventHandlers.Remove((eventHandler, eventType)); + } + else + { + _disposableEventHandlers[(eventHandler, eventType)] = (adviseCountAndDisposer.adviseCount - 1, adviseCountAndDisposer.disposer); + } + } + } + + disposer?.Dispose(); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Registration.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Registration.cs index fe7c7ca57253c..ea11c37658788 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Registration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Registration.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.WorkspaceEventMap; namespace Microsoft.CodeAnalysis; @@ -42,7 +43,13 @@ protected void RegisterText(SourceTextContainer textContainer) var registration = GetWorkspaceRegistration(textContainer); registration.SetWorkspace(this); - this.ScheduleTask(registration.RaiseEvents, "Workspace.RegisterText"); + + // Require main thread on the callback as WorkspaceRegistration.RaiseEvents invokes Workspace.WorkspaceChanges which is publicly exposed + // and the event handlers may have main thread dependencies. Potential cleanup here with relation to + // https://github.com/dotnet/roslyn/issues/32551 + var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => registration.RaiseEvents(), WorkspaceEventOptions.RequiresMainThreadOptions); + var handlerSet = EventHandlerSet.Create(handlerAndOptions); + this.ScheduleTask(EventArgs.Empty, handlerSet); } /// diff --git a/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs b/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs index 617c0cdcc393d..d6a71b46fb465 100644 --- a/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceTests/AdhocWorkspaceTests.cs @@ -420,14 +420,14 @@ public async Task TestChangeDocumentName_TryApplyChanges() Assert.Equal(newName, changedDoc.Name); var tcs = new TaskCompletionSource(); - ws.WorkspaceChanged += (s, args) => + using var _ = ws.RegisterWorkspaceChangedHandler(args => { if (args.Kind == WorkspaceChangeKind.DocumentInfoChanged && args.DocumentId == originalDoc.Id) { tcs.SetResult(true); } - }; + }); Assert.True(ws.TryApplyChanges(changedDoc.Project.Solution)); @@ -453,14 +453,14 @@ public async Task TestChangeDocumentFolders_TryApplyChanges() Assert.Equal("B", changedDoc.Folders[1]); var tcs = new TaskCompletionSource(); - ws.WorkspaceChanged += (s, args) => + using var _ = ws.RegisterWorkspaceChangedHandler(args => { if (args.Kind == WorkspaceChangeKind.DocumentInfoChanged && args.DocumentId == originalDoc.Id) { tcs.SetResult(true); } - }; + }); Assert.True(ws.TryApplyChanges(changedDoc.Project.Solution)); @@ -487,14 +487,14 @@ public async Task TestChangeDocumentFilePath_TryApplyChanges() Assert.Equal(newPath, changedDoc.FilePath); var tcs = new TaskCompletionSource(); - ws.WorkspaceChanged += (s, args) => + using var _ = ws.RegisterWorkspaceChangedHandler(args => { if (args.Kind == WorkspaceChangeKind.DocumentInfoChanged && args.DocumentId == originalDoc.Id) { tcs.SetResult(true); } - }; + }); Assert.True(ws.TryApplyChanges(changedDoc.Project.Solution)); @@ -518,14 +518,14 @@ public async Task TestChangeDocumentSourceCodeKind_TryApplyChanges() Assert.Equal(SourceCodeKind.Script, changedDoc.SourceCodeKind); var tcs = new TaskCompletionSource(); - ws.WorkspaceChanged += (s, args) => + using var _ = ws.RegisterWorkspaceChangedHandler(args => { if (args.Kind == WorkspaceChangeKind.DocumentInfoChanged && args.DocumentId == originalDoc.Id) { tcs.SetResult(true); } - }; + }); Assert.True(ws.TryApplyChanges(changedDoc.Project.Solution)); diff --git a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs index da5f1a3cc3091..4fb64efe2d2a6 100644 --- a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs +++ b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs @@ -57,7 +57,7 @@ public static IEnumerable GetProjectsByName(this Solution solution, str internal static EventWaiter VerifyWorkspaceChangedEvent(this Workspace workspace, Action action) { var wew = new EventWaiter(); - workspace.WorkspaceChanged += wew.Wrap((sender, args) => action(args)); + _ = workspace.RegisterWorkspaceChangedHandler(wew.Wrap(action)); return wew; } } diff --git a/src/Workspaces/MSBuild/Test/MSBuildWorkspaceTestBase.cs b/src/Workspaces/MSBuild/Test/MSBuildWorkspaceTestBase.cs index 97bb16c6fb3de..a94c9a22054d6 100644 --- a/src/Workspaces/MSBuild/Test/MSBuildWorkspaceTestBase.cs +++ b/src/Workspaces/MSBuild/Test/MSBuildWorkspaceTestBase.cs @@ -164,7 +164,7 @@ protected MSBuildWorkspace CreateMSBuildWorkspace( if (throwOnWorkspaceFailed) { - workspace.WorkspaceFailed += (s, e) => throw new Exception($"Workspace failure {e.Diagnostic.Kind}:{e.Diagnostic.Message}"); + _ = workspace.RegisterWorkspaceFailedHandler((e) => throw new Exception($"Workspace failure {e.Diagnostic.Kind}:{e.Diagnostic.Message}")); } if (skipUnrecognizedProjects) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index d6707a6081612..c05dfea8823bd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -508,7 +508,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EventMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EventMap.cs deleted file mode 100644 index c7e66cb79fe3e..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/EventMap.cs +++ /dev/null @@ -1,176 +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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.ErrorReporting; - -namespace Roslyn.Utilities; - -internal sealed class EventMap -{ - private readonly NonReentrantLock _guard = new(); - - private readonly Dictionary _eventNameToRegistries = []; - - public EventMap() - { - } - - public void AddEventHandler(string eventName, TEventHandler eventHandler) - where TEventHandler : class - { - using (_guard.DisposableWait()) - { - var registries = GetRegistries_NoLock(eventName); - var newRegistries = registries.Add(new Registry(eventHandler)); - SetRegistries_NoLock(eventName, newRegistries); - } - } - - public void RemoveEventHandler(string eventName, TEventHandler eventHandler) - where TEventHandler : class - { - using (_guard.DisposableWait()) - { - var registries = GetRegistries_NoLock(eventName); - - // remove disabled registrations from list - var newRegistries = registries.RemoveAll(r => r.HasHandler(eventHandler)); - - if (newRegistries != registries) - { - // disable all registrations of this handler (so pending raise events can be squelched) - // This does not guarantee no race condition between Raise and Remove but greatly reduces it. - foreach (var registry in registries.Where(r => r.HasHandler(eventHandler))) - { - registry.Unregister(); - } - - SetRegistries_NoLock(eventName, newRegistries); - } - } - } - - [PerformanceSensitive( - "https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", - AllowImplicitBoxing = false)] - public EventHandlerSet GetEventHandlers(string eventName) - where TEventHandler : class - { - return new EventHandlerSet(this.GetRegistries(eventName)); - } - - private ImmutableArray> GetRegistries(string eventName) - where TEventHandler : class - { - using (_guard.DisposableWait()) - { - return GetRegistries_NoLock(eventName); - } - } - - private ImmutableArray> GetRegistries_NoLock(string eventName) - where TEventHandler : class - { - _guard.AssertHasLock(); - if (_eventNameToRegistries.TryGetValue(eventName, out var registries)) - { - return (ImmutableArray>)registries; - } - - return []; - } - - private void SetRegistries_NoLock(string eventName, ImmutableArray> registries) - where TEventHandler : class - { - _guard.AssertHasLock(); - - _eventNameToRegistries[eventName] = registries; - } - - internal sealed class Registry(TEventHandler handler) : IEquatable?> - where TEventHandler : class - { - private TEventHandler? _handler = handler; - - public void Unregister() - => _handler = null; - - public void Invoke(Action invoker, TArg arg) - { - var handler = _handler; - if (handler != null) - { - invoker(handler, arg); - } - } - - public bool HasHandler(TEventHandler handler) - => handler.Equals(_handler); - - public bool Equals(Registry? other) - { - if (other == null) - { - return false; - } - - if (other._handler == null && _handler == null) - { - return true; - } - - if (other._handler == null || _handler == null) - { - return false; - } - - return other._handler.Equals(_handler); - } - - public override bool Equals(object? obj) - => Equals(obj as Registry); - - public override int GetHashCode() - => _handler == null ? 0 : _handler.GetHashCode(); - } - - internal struct EventHandlerSet - where TEventHandler : class - { - private readonly ImmutableArray> _registries; - - internal EventHandlerSet(ImmutableArray> registries) - => _registries = registries; - - public readonly bool HasHandlers - { - get { return _registries != null && _registries.Length > 0; } - } - - public readonly void RaiseEvent(Action invoker, TArg arg) - { - if (this.HasHandlers) - { - foreach (var registry in _registries) - { - try - { - registry.Invoke(invoker, arg); - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - // Catch the exception and continue as this an event handler and propagating the exception would prevent - // other handlers from executing - } - } - } - } - } -} From 6abc1a7fd3995189fc636a803b4a6da2ff58f7d6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sun, 27 Apr 2025 11:45:25 +1000 Subject: [PATCH 068/114] Allow Razor logs to be directed to the right output window in VS Code --- .../Logging/LspLogMessageLogger.cs | 18 ++++++++++++++++-- .../Logging/LspLoggingScope.cs | 2 +- .../Logging/LspServiceLogger.cs | 7 ++++++- .../AbstractLspLogger.cs | 1 + .../ILspLogger.cs | 2 ++ .../RequestExecutionQueue.cs | 2 ++ .../LanguageServer/TestOutputLspLogger.cs | 1 + src/LanguageServer/Protocol/NoOpLspLogger.cs | 1 + .../Cohost/RazorStartupServiceFactory.cs | 2 ++ .../Core/Def/LanguageClient/LogHubLspLogger.cs | 1 + 10 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs index 182a6d8b9c382..32aa9b560615d 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLogMessageLogger.cs @@ -51,11 +51,25 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except string messagePrefix = ""; + var logMethod = Methods.WindowLogMessageName; + _externalScopeProvider?.ForEachScope((scope, _) => { if (scope is LspLoggingScope lspLoggingScope) { - messagePrefix += $"[{lspLoggingScope.Context}] "; + if (lspLoggingScope.Context is not null) + { + messagePrefix += $"[{lspLoggingScope.Context}] "; + } + + if (lspLoggingScope.Language is not null) + { + logMethod = lspLoggingScope.Language switch + { + LanguageInfoProvider.RazorLanguageName => "razor/log", + _ => logMethod, + }; + } } }, state); @@ -63,7 +77,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except try { - var _ = server.GetRequiredLspService().SendNotificationAsync(Methods.WindowLogMessageName, new LogMessageParams() + var _ = server.GetRequiredLspService().SendNotificationAsync(logMethod, new LogMessageParams() { Message = $"{messagePrefix} {message}", MessageType = LogLevelToMessageType(logLevel), diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLoggingScope.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLoggingScope.cs index c5df92175269e..2240049c4a12d 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLoggingScope.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspLoggingScope.cs @@ -4,5 +4,5 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Logging { - internal sealed record LspLoggingScope(string Context); + internal sealed record LspLoggingScope(string? Context, string? Language); } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspServiceLogger.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspServiceLogger.cs index 37243b44426a3..08fb6743c888d 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspServiceLogger.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Logging/LspServiceLogger.cs @@ -19,7 +19,12 @@ public LspServiceLogger(ILogger hostLogger) _hostLogger = hostLogger; } - public override IDisposable? CreateContext(string context) => _hostLogger.BeginScope(new LspLoggingScope(context)); + public override IDisposable? CreateContext(string context) => _hostLogger.BeginScope(new LspLoggingScope(context, null)); + + public override IDisposable? CreateLanguageContext(string? language) + => language is null + ? null + : _hostLogger.BeginScope(new LspLoggingScope(null, language)); public override void LogDebug(string message, params object[] @params) => _hostLogger.LogDebug(message, @params); diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs index 52f557c278f72..f3d2450e24a1a 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs @@ -12,6 +12,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; internal abstract class AbstractLspLogger : ILspLogger { public abstract IDisposable? CreateContext(string context); + public abstract IDisposable? CreateLanguageContext(string? language); public abstract void LogDebug(string message, params object[] @params); public abstract void LogInformation(string message, params object[] @params); diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs index 3ca7393e24989..47cf0acda452c 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs @@ -8,9 +8,11 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; + internal interface ILspLogger { IDisposable? CreateContext(string context); + IDisposable? CreateLanguageContext(string? language); void LogDebug(string message, params object[] @params); void LogInformation(string message, params object[] @params); diff --git a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs index 9d826808bf7a4..a45651f422bab 100644 --- a/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs +++ b/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs @@ -248,6 +248,8 @@ private async Task ProcessQueueAsync() // the didOpen, ensuring that this line will only run once all prior didOpens have completed. var didGetLanguage = _languageServer.TryGetLanguageForRequest(work.MethodName, work.SerializedRequest, out var language); + using var languageScope = _logger.CreateLanguageContext(language); + // Now that we know the actual language, we can deserialize the request and start creating the request context. var (metadata, handler, methodInfo) = GetHandlerForRequest(work, language ?? LanguageServerConstants.DefaultLanguageName); diff --git a/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs index dabe457f7e5eb..3957a0bacd2f8 100644 --- a/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs @@ -18,6 +18,7 @@ public TestOutputLspLogger(ITestOutputHelper testOutputHelper) } public override IDisposable? CreateContext(string context) => null; + public override IDisposable? CreateLanguageContext(string? context) => null; public override void LogDebug(string message, params object[] @params) => Log("Debug", message, @params); diff --git a/src/LanguageServer/Protocol/NoOpLspLogger.cs b/src/LanguageServer/Protocol/NoOpLspLogger.cs index f0d06c334e490..13b951a853f7f 100644 --- a/src/LanguageServer/Protocol/NoOpLspLogger.cs +++ b/src/LanguageServer/Protocol/NoOpLspLogger.cs @@ -14,6 +14,7 @@ internal sealed class NoOpLspLogger : AbstractLspLogger, ILspService private NoOpLspLogger() { } public override IDisposable? CreateContext(string context) => null; + public override IDisposable? CreateLanguageContext(string? context) => null; public override void LogDebug(string message, params object[] @params) { diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs index 1c9501ea4bb1a..1a77a2921be10 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/RazorStartupServiceFactory.cs @@ -83,6 +83,8 @@ private async Task InitializeRazorAsync(ClientCapabilities clientCapabilities, R await TaskScheduler.Default.SwitchTo(alwaysYield: true); + using var languageScope = context.Logger.CreateLanguageContext(Constants.RazorLanguageName); + // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL var serializedClientCapabilities = JsonSerializer.Serialize(clientCapabilities, ProtocolConversions.LspJsonSerializerOptions); diff --git a/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs b/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs index 78c011159e310..ef4ad93c432d8 100644 --- a/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs +++ b/src/VisualStudio/Core/Def/LanguageClient/LogHubLspLogger.cs @@ -38,6 +38,7 @@ public void Dispose() } public override IDisposable? CreateContext(string context) => null; + public override IDisposable? CreateLanguageContext(string? language) => null; public override void LogDebug(string message, params object[] @params) { From aa2eca488bb8c61a9baa011d3e2234a94a6781ef Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 27 Apr 2025 02:02:24 +0000 Subject: [PATCH 069/114] Update dependencies from https://github.com/dotnet/arcade build 20250425.6 Microsoft.SourceBuild.Intermediate.arcade , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.XliffTasks From Version 9.0.0-beta.25208.6 -> To Version 9.0.0-beta.25225.6 --- eng/Version.Details.xml | 16 ++++++++-------- eng/common/core-templates/job/source-build.yml | 2 ++ .../core-templates/job/source-index-stage1.yml | 4 ++-- eng/common/core-templates/steps/source-build.yml | 1 + global.json | 4 ++-- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f7f8127bad495..e47333423a73c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -128,19 +128,19 @@ - + https://github.com/dotnet/arcade - aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 + bfbc858ba868b60fffaf7b2150f1d2165b01e786 - + https://github.com/dotnet/arcade - aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 + bfbc858ba868b60fffaf7b2150f1d2165b01e786 - + https://github.com/dotnet/arcade - aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 + bfbc858ba868b60fffaf7b2150f1d2165b01e786 https://github.com/dotnet/symreader @@ -156,9 +156,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 + bfbc858ba868b60fffaf7b2150f1d2165b01e786 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/core-templates/job/source-build.yml b/eng/common/core-templates/job/source-build.yml index c4713c8b6ede8..d47f09d58fd9a 100644 --- a/eng/common/core-templates/job/source-build.yml +++ b/eng/common/core-templates/job/source-build.yml @@ -26,6 +26,8 @@ parameters: # Specifies the build script to invoke to perform the build in the repo. The default # './build.sh' should work for typical Arcade repositories, but this is customizable for # difficult situations. + # buildArguments: '' + # Specifies additional build arguments to pass to the build script. # jobProperties: {} # A list of job properties to inject at the top level, for potential extensibility beyond # container and pool. diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml index 205fb5b3a3956..8b833332b3ee9 100644 --- a/eng/common/core-templates/job/source-index-stage1.yml +++ b/eng/common/core-templates/job/source-index-stage1.yml @@ -1,7 +1,7 @@ parameters: runAsPublic: false - sourceIndexUploadPackageVersion: 2.0.0-20240522.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20240522.1 + sourceIndexUploadPackageVersion: 2.0.0-20250425.2 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250425.2 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml index 2915d29bb7f6e..37133b55b7541 100644 --- a/eng/common/core-templates/steps/source-build.yml +++ b/eng/common/core-templates/steps/source-build.yml @@ -79,6 +79,7 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack $publishArgs -bl \ + ${{ parameters.platform.buildArguments }} \ $officialBuildArgs \ $internalRuntimeDownloadArgs \ $internalRestoreArgs \ diff --git a/global.json b/global.json index 2d5b35de406e3..a70efc932fb72 100644 --- a/global.json +++ b/global.json @@ -11,8 +11,8 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25208.6", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25208.6", + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25225.6", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25225.6", "Microsoft.Build.Traversal": "3.4.0" } } From aafb3c3210bddc87bfabf06e219432291f371c0a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 26 Apr 2025 22:05:14 -0700 Subject: [PATCH 070/114] Do not offer 'remove unnecessary parens' when it would change a collection initializer --- ...veUnnecessaryExpressionParenthesesTests.cs | 28 +++++++++++++++++++ ...ParenthesizedExpressionSyntaxExtensions.cs | 27 ++++++++++-------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs index 3cec58cdba30c..29ee35446cc0a 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryParentheses/RemoveUnnecessaryExpressionParenthesesTests.cs @@ -3552,4 +3552,32 @@ public void M() } """); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78331")] + public async Task TestCollectionExpressionInInitializer() + { + await TestMissingInRegularAndScriptAsync(""" + class C + { + public void M() + { + var v = new List() { $$([]) }; + } + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78331")] + public async Task TestAttributedLambdaExpressionInInitializer() + { + await TestMissingInRegularAndScriptAsync(""" + class C + { + public void M() + { + var v = new List() { $$([X] () => { }) }; + } + } + """); + } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs index 929b8052de79a..1df2ac609efaf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs @@ -110,15 +110,29 @@ public static bool CanRemoveParentheses( if (expression.IsKind(SyntaxKind.TupleExpression)) return true; + // Cases: + // {(x)} -> {x} + if (nodeParent is InitializerExpressionSyntax) + { + // `{ ([]) }` can't become `{ [] }` as `[` in an initializer will be parsed as an index assignment. + if (tokenAfterParen.Kind() == SyntaxKind.OpenBracketToken) + return false; + + // Assignment expressions and collection expressions are not allowed in initializers + // as they are not parsed as expressions, but as more complex constructs + if (expression is AssignmentExpressionSyntax) + return false; + + return true; + } + // ([...]) -> [...] if (expression.IsKind(SyntaxKind.CollectionExpression)) return true; // int Prop => (x); -> int Prop => x; if (nodeParent is ArrowExpressionClauseSyntax arrowExpressionClause && arrowExpressionClause.Expression == node) - { return true; - } // Easy statement-level cases: // var y = (x); -> var y = x; @@ -181,15 +195,6 @@ public static bool CanRemoveParentheses( if (expression.IsKind(SyntaxKind.InterpolatedStringExpression)) return true; - // Cases: - // {(x)} -> {x} - if (nodeParent is InitializerExpressionSyntax) - { - // Assignment expressions and collection expressions are not allowed in initializers - // as they are not parsed as expressions, but as more complex constructs - return expression is not AssignmentExpressionSyntax and not CollectionExpressionSyntax; - } - // Cases: // new {(x)} -> {x} // new { a = (x)} -> { a = x } From 73f70f4b5d6565b12cdf8ded67cfbc2a0107602d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 28 Apr 2025 01:54:18 -0700 Subject: [PATCH 071/114] Extensions: honor OverloadResolutionPriority attribute (#78219) --- .../OverloadResolution/OverloadResolution.cs | 10 +- .../RewrittenLambdaOrLocalFunctionSymbol.cs | 3 + ...urceExtensionImplementationMethodSymbol.cs | 21 + .../Symbols/Metadata/PE/PEPropertySymbol.cs | 2 +- .../Symbols/NativeIntegerTypeSymbol.cs | 3 + .../CSharp/Portable/Symbols/PropertySymbol.cs | 3 +- .../Retargeting/RetargetingMethodSymbol.cs | 3 + .../Source/SourcePropertySymbolBase.cs | 4 +- .../Symbols/SubstitutedMethodSymbol.cs | 3 + .../Symbols/Wrapped/WrappedMethodSymbol.cs | 5 - .../Test/Emit3/Semantics/ExtensionTests.cs | 566 +++++++++++++++++- .../Symbols/EECompilationContextMethod.cs | 5 + 12 files changed, 606 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 6a785b95c97fc..35e2a9380769f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -1878,7 +1878,8 @@ private void RemoveLowerPriorityMembers(ArrayBuilder foreach (var result in results) { - Debug.Assert(result.MemberWithPriority is not null); + TMember memberWithPriority = result.MemberWithPriority; + Debug.Assert(memberWithPriority is not null); // We don't filter out inapplicable members here, as we want to keep them in the list for diagnostics // However, we don't want to take them into account for the priority filtering @@ -1888,11 +1889,14 @@ private void RemoveLowerPriorityMembers(ArrayBuilder continue; } - var containingType = result.MemberWithPriority.ContainingType; // Tracked by https://github.com/dotnet/roslyn/issues/76130 : how should ORPA apply to new extension methods? + NamedTypeSymbol containingType = memberWithPriority.GetIsNewExtensionMember() + ? memberWithPriority.ContainingType.ContainingType + : memberWithPriority.ContainingType; + if (resultsByContainingType.TryGetValue(containingType, out var previousResults)) { var previousPriority = previousResults.First().MemberWithPriority.GetOverloadResolutionPriority(); - var currentPriority = result.MemberWithPriority.GetOverloadResolutionPriority(); + var currentPriority = memberWithPriority.GetOverloadResolutionPriority(); if (currentPriority > previousPriority) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenLambdaOrLocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenLambdaOrLocalFunctionSymbol.cs index f20efc3f1f199..5889913265f55 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenLambdaOrLocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenLambdaOrLocalFunctionSymbol.cs @@ -30,6 +30,9 @@ internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) return true; } + internal override int TryGetOverloadResolutionPriority() + => _originalMethod.TryGetOverloadResolutionPriority(); + protected override ImmutableArray MakeParameters() { return ImmutableArray.CastUp(_originalMethod.Parameters.SelectAsArray(static (p, @this) => new RewrittenMethodParameterSymbol(@this, p), this)); diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/SourceExtensionImplementationMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/SourceExtensionImplementationMethodSymbol.cs index 444dc3b350a73..1201586ed0ee4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/SourceExtensionImplementationMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/SourceExtensionImplementationMethodSymbol.cs @@ -65,6 +65,17 @@ internal override int ParameterCount internal sealed override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) { + if (_originalMethod is SourcePropertyAccessorSymbol { AssociatedSymbol: SourcePropertySymbolBase extensionProperty }) + { + foreach (CSharpAttributeData attr in extensionProperty.GetAttributes()) + { + if (attr.IsTargetAttribute(AttributeDescription.OverloadResolutionPriorityAttribute)) + { + AddSynthesizedAttribute(ref attributes, attr); + } + } + } + base.AddSynthesizedAttributes(moduleBuilder, ref attributes); SourceMethodSymbol.AddSynthesizedAttributes(this, moduleBuilder, ref attributes); } @@ -110,6 +121,16 @@ internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) return true; } + internal override int TryGetOverloadResolutionPriority() + { + if (UnderlyingMethod is SourcePropertyAccessorSymbol { AssociatedSymbol: SourcePropertySymbol property }) + { + return property.TryGetOverloadResolutionPriority(); + } + + return UnderlyingMethod.TryGetOverloadResolutionPriority(); + } + private sealed class ExtensionMetadataMethodParameterSymbol : RewrittenMethodParameterSymbol { public ExtensionMetadataMethodParameterSymbol(SourceExtensionImplementationMethodSymbol containingMethod, ParameterSymbol sourceParameter) : diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEPropertySymbol.cs index 86e4c0dd8c549..15e2319ae87ab 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEPropertySymbol.cs @@ -1045,7 +1045,7 @@ internal override bool HasRuntimeSpecialName internal override int TryGetOverloadResolutionPriority() { - Debug.Assert(IsIndexer || IsIndexedProperty); + Debug.Assert(IsIndexer || IsIndexedProperty || this.GetIsNewExtensionMember()); if (!_flags.IsOverloadResolutionPriorityPopulated) { if (_containingType.ContainingPEModule.Module.TryGetOverloadResolutionPriorityValue(_handle, out int priority) && diff --git a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs index e28cfb4818494..1364dedfc3cc8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs @@ -399,6 +399,9 @@ internal sealed override bool HasAsyncMethodBuilderAttribute(out TypeSymbol? bui builderArgument = null; return false; } + + internal override int TryGetOverloadResolutionPriority() + => UnderlyingMethod.TryGetOverloadResolutionPriority(); } internal sealed class NativeIntegerParameterSymbol : WrappedParameterSymbol diff --git a/src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs index 40946df3c5950..7b81380e15d4d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs @@ -356,7 +356,8 @@ internal int OverloadResolutionPriority internal abstract int TryGetOverloadResolutionPriority(); - internal bool CanHaveOverloadResolutionPriority => !IsOverride && !IsExplicitInterfaceImplementation && (IsIndexer || IsIndexedProperty); + internal bool CanHaveOverloadResolutionPriority + => !IsOverride && !IsExplicitInterfaceImplementation && (IsIndexer || IsIndexedProperty || this.GetIsNewExtensionMember()); /// /// Implements visitor pattern. diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs index ffe6a6ec6955d..3254b2c21a799 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs @@ -269,6 +269,9 @@ internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) : null; return true; } + + internal override int TryGetOverloadResolutionPriority() + => _underlyingMethod.TryGetOverloadResolutionPriority(); #nullable disable public override AssemblySymbol ContainingAssembly diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index fb6bc5435ae91..617e5edc970c6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -1470,7 +1470,7 @@ internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAt return (null, null); } - else if (IsIndexer && CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.OverloadResolutionPriorityAttribute)) + else if ((IsIndexer || this.GetIsNewExtensionMember()) && CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.OverloadResolutionPriorityAttribute)) { (attributeData, boundAttribute) = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, beforeAttributePartBound: null, afterAttributePartBound: null, out var hasAnyDiagnostics); @@ -1733,7 +1733,7 @@ private void ValidateIndexerNameAttribute(CSharpAttributeData attribute, Attribu internal sealed override int TryGetOverloadResolutionPriority() { - Debug.Assert(this.IsIndexer); + Debug.Assert(this.IsIndexer || this.GetIsNewExtensionMember()); return GetEarlyDecodedWellKnownAttributeData()?.OverloadResolutionPriority ?? 0; } diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs index 8887c4520419b..86494a5ead675 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs @@ -329,6 +329,9 @@ internal sealed override bool TryGetThisParameter(out ParameterSymbol thisParame return true; } + internal override int TryGetOverloadResolutionPriority() + => OriginalDefinition.TryGetOverloadResolutionPriority(); + private ImmutableArray SubstituteParameters() { var unsubstitutedParameters = OriginalDefinition.Parameters; diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs index 2087167575af1..5f72a26a17330 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs @@ -364,10 +364,5 @@ internal override bool GenerateDebugInfo internal sealed override bool HasUnscopedRefAttribute => UnderlyingMethod.HasUnscopedRefAttribute; internal sealed override bool UseUpdatedEscapeRules => UnderlyingMethod.UseUpdatedEscapeRules; - - internal sealed override int TryGetOverloadResolutionPriority() - { - return UnderlyingMethod.TryGetOverloadResolutionPriority(); - } } } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 5ab83216522c8..af139c7a228fc 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -25905,6 +25905,51 @@ static class E1 [Fact] public void MethodInvocation_RemoveLowerPriorityMembers_02() + { + var libSrc = """ +public static class E1 +{ + extension(int i) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + public void M(int j) => throw null; + } + extension(int i) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public void M(long l) { System.Console.Write("ran"); } + } +} +"""; + var source = """ +42.M(43); +"""; + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran", symbolValidator: verify).VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "42.M"); + Assert.Equal("void E1.<>E__1.M(System.Int64 l)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation(source, references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + + static void verify(ModuleSymbol m) + { + var implementations = m.ContainingAssembly.GetTypeByMetadataName("E1").GetMembers().OfType().ToArray(); + + AssertEx.Equal("void E1.M(this System.Int32 i, System.Int32 j)", implementations[0].ToTestDisplayString()); + AssertEx.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(0)", implementations[0].GetAttributes().Single().ToString()); + + AssertEx.Equal("void E1.M(this System.Int32 i, System.Int64 l)", implementations[1].ToTestDisplayString()); + AssertEx.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(1)", implementations[1].GetAttributes().Single().ToString()); + } + } + + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_03() { var source = """ 42.M(43); @@ -25913,27 +25958,48 @@ static class E1 { extension(int i) { - public void M(int j) { System.Console.Write("ran"); } + public void M(int j) => throw null; + } + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public static void M(this int i, long l) { System.Console.Write("ran"); } +} +"""; + var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "42.M"); + Assert.Equal("void System.Int32.M(System.Int64 l)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); } + + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_04() + { + var source = """ +42.M(43); + +static class E1 +{ + public static void M(this int i, int j) => throw null; extension(int i) { [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] - public void M(long l) => throw null; + public void M(long l) { System.Console.Write("ran"); } } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : ORPA should look at the containing static class (rather than look at the extension declaration) as the "containing type" var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntax(tree, "42.M"); - Assert.Equal("void E1.<>E__0.M(System.Int32 j)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + Assert.Equal("void E1.<>E__0.M(System.Int64 l)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); } [Fact] - public void MethodInvocation_RemoveLowerPriorityMembers_03() + public void MethodInvocation_RemoveLowerPriorityMembers_05() { var source = """ 42.M(43); @@ -25944,11 +26010,16 @@ static class E1 { public void M(int j) { System.Console.Write("ran"); } } - [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] - public static void M(this int i, long l) => throw null; +} +static class E2 +{ + extension(int i) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public void M(long l) => throw null; + } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : ORPA should look at the containing static class (rather than look at the extension declaration) as the "containing type" var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); @@ -25959,7 +26030,7 @@ static class E1 } [Fact] - public void MethodInvocation_RemoveLowerPriorityMembers_04() + public void MethodInvocation_RemoveLowerPriorityMembers_06() { var source = """ 42.M(43); @@ -25967,6 +26038,9 @@ public void MethodInvocation_RemoveLowerPriorityMembers_04() static class E1 { public static void M(this int i, int j) { System.Console.Write("ran"); } +} +static class E2 +{ extension(int i) { [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] @@ -25974,7 +26048,6 @@ static class E1 } } """; - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : ORPA should look at the containing static class (rather than look at the extension declaration) as the "containing type" var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); @@ -25984,6 +26057,427 @@ static class E1 Assert.Equal("void System.Int32.M(System.Int32 j)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); } + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_07() + { + var source = """ +string s = ""; +s.M(); + +static class E1 +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public void M() { System.Console.Write("ran"); } + } + extension(string s) + { + public void M() => throw null; + } +} +"""; + var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "s.M"); + Assert.Equal("void E1.<>E__0.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_08() + { + var source = """ +string s = "ran"; +System.Console.Write(s.ToString()); + +static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public string ToString() => throw null; + } +} +"""; + var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_09() + { + var libSrc = """ +public static class E +{ + extension(int i) + { + public void M() => throw null; + } + extension(long l) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public void M() { System.Console.Write("ran"); } + } +} +"""; + var source = """ +E.M(43); +"""; + + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation([source], references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void MethodInvocation_RemoveLowerPriorityMembers_10() + { + var libSrc = """ +public static class E +{ + extension(int) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + public static void M(int j) => throw null; + } + extension(int) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public static void M(long l) { System.Console.Write("ran"); } + } +} +"""; + var source = """ +int.M(43); +"""; + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran", symbolValidator: verify).VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "int.M"); + Assert.Equal("void E.<>E__1.M(System.Int64 l)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation(source, references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + + static void verify(ModuleSymbol m) + { + var implementations = m.ContainingAssembly.GetTypeByMetadataName("E").GetMembers().OfType().ToArray(); + + AssertEx.Equal("void E.M(System.Int32 j)", implementations[0].ToTestDisplayString()); + AssertEx.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(0)", implementations[0].GetAttributes().Single().ToString()); + + AssertEx.Equal("void E.M(System.Int64 l)", implementations[1].ToTestDisplayString()); + AssertEx.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(1)", implementations[1].GetAttributes().Single().ToString()); + } + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_01() + { + var source = """ +string s = ""; +_ = s.P; + +static class E +{ + extension(object o) + { + public long P => throw null; + } + extension(string s) + { + public int P { get { System.Console.Write("ran"); return 0; } } + } +} +"""; + var comp = CreateCompilation(source); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "s.P"); + Assert.Equal("System.Int32 E.<>E__1.P { get; }", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_02() + { + var libSrc = """ +public static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public int P { get { System.Console.Write("ran"); return 0; } } + } + extension(string s) + { + public long P => throw null; + } +} +"""; + + var source = """ +string s = ""; +_ = s.P; +"""; + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "s.P"); + Assert.Equal("System.Int32 E.<>E__0.P { get; }", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation([source], references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_03() + { + var source = """ +string s = ""; +_ = s.P; + +static class E1 +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public long P => throw null; + } +} +static class E2 +{ + extension(string s) + { + public int P { get { System.Console.Write("ran"); return 0; } } + } +} +"""; + var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "s.P"); + Assert.Equal("System.Int32 E2.<>E__0.P { get; }", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_04() + { + var source = """ +static class E +{ + extension(object o) + { + public int P + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + get => throw null; + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + set => throw null; + } + } +} +"""; + var comp = CreateCompilation([source, OverloadResolutionPriorityAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (7,14): error CS9262: Cannot use 'OverloadResolutionPriorityAttribute' on this member. + // [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + Diagnostic(ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToMember, "System.Runtime.CompilerServices.OverloadResolutionPriority(1)").WithLocation(7, 14), + // (9,14): error CS9262: Cannot use 'OverloadResolutionPriorityAttribute' on this member. + // [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + Diagnostic(ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToMember, "System.Runtime.CompilerServices.OverloadResolutionPriority(1)").WithLocation(9, 14)); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_05() + { + var libSrc = """ +public static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public int P { get { System.Console.Write("ran"); return 0; } } + } + extension(string s) + { + public long P => throw null; + } +} +"""; + + var source = """ +E.get_P(""); +"""; + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran", symbolValidator: verify).VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "E.get_P"); + Assert.Equal("System.Int32 E.get_P(System.Object o)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation([source], references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + + static void verify(ModuleSymbol m) + { + var implementations = m.ContainingAssembly.GetTypeByMetadataName("E").GetMembers().OfType().ToArray(); + + Assert.Equal("System.Int32 E.get_P(System.Object o)", implementations[0].ToTestDisplayString()); + Assert.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(1)", implementations[0].GetAttributes().Single().ToString()); + + Assert.Equal("System.Int64 E.get_P(System.String s)", implementations[1].ToTestDisplayString()); + Assert.Empty(implementations[1].GetAttributes()); + } + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_06() + { + var libSrc = """ +public static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public int P { set { System.Console.Write("ran"); } } + } + extension(string s) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + public int P { set => throw null; } + } +} +"""; + + var source = """ +E.set_P("", 0); +"""; + var comp = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp, expectedOutput: "ran", symbolValidator: verify).VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "E.set_P"); + Assert.Equal("void E.set_P(System.Object o, System.Int32 value)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + var libComp = CreateCompilation([libSrc, OverloadResolutionPriorityAttributeDefinition]); + var comp2 = CreateCompilation([source], references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + + static void verify(ModuleSymbol m) + { + var implementations = m.ContainingAssembly.GetTypeByMetadataName("E").GetMembers().OfType().ToArray(); + + Assert.Equal("void E.set_P(System.Object o, System.Int32 value)", implementations[0].ToTestDisplayString()); + Assert.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(1)", implementations[0].GetAttributes().Single().ToString()); + + Assert.Equal("void E.set_P(System.String s, System.Int32 value)", implementations[1].ToTestDisplayString()); + Assert.Equal("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(0)", implementations[1].GetAttributes().Single().ToString()); + } + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_07() + { + var source = """ +public static class E +{ + extension(object o) + { + public static int P => 0; + } + extension(string s) + { + public static int P => 0; + } +} +"""; + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (9,32): error CS0111: Type 'E' already defines a member called 'get_P' with the same parameter types + // public static int P => 0; + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "0").WithArguments("get_P", "E").WithLocation(9, 32)); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_08() + { + var source = """ +static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + public int P => throw null; + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (5,42): error CS0234: The type or namespace name 'OverloadResolutionPriorityAttribute' does not exist in the namespace 'System.Runtime.CompilerServices' (are you missing an assembly reference?) + // [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInNS, "OverloadResolutionPriority").WithArguments("OverloadResolutionPriorityAttribute", "System.Runtime.CompilerServices").WithLocation(5, 42), + // (5,42): error CS0234: The type or namespace name 'OverloadResolutionPriority' does not exist in the namespace 'System.Runtime.CompilerServices' (are you missing an assembly reference?) + // [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInNS, "OverloadResolutionPriority").WithArguments("OverloadResolutionPriority", "System.Runtime.CompilerServices").WithLocation(5, 42)); + } + + [Fact] + public void PropertyAccess_RemoveLowerPriorityMembers_09() + { + var source = """ +public static class E +{ + extension(object o) + { + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + [System.Runtime.CompilerServices.OverloadResolutionPriority(2)] + public int P { set { } } + } +} + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = true, Inherited = false)] + public sealed class OverloadResolutionPriorityAttribute(int priority) : Attribute + { + public int Priority => priority; + } +} +"""; + + var comp = CreateCompilation(source); + CompileAndVerify(comp, symbolValidator: verify).VerifyDiagnostics(); + + static void verify(ModuleSymbol m) + { + var implementation = m.ContainingAssembly.GetTypeByMetadataName("E").GetMembers().OfType().Single(); + AssertEx.SetEqual([ + "System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(1)", + "System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(2)"], + implementation.GetAttributes().ToStrings()); + } + } + [Fact] public void MethodInvocation_RemoveStaticInstanceMismatches_01() { @@ -36055,4 +36549,56 @@ .method public hidebysig specialname static int32 get_P2 ( int32 '' ) cil manage var comp = CreateCompilationWithIL(source, ilSrc); comp.VerifyEmitDiagnostics(); } + + [Fact] + public void SkipLocalsInit_01() + { + string source = """ +static unsafe class E +{ + extension(int i) + { + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public int P + { + get { int j = 0; return j; } + } + + public int P2 + { + get { int j = 0; return j; } + } + } +} +"""; + + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped); + Assert.False(verifier.HasLocalsInit("E.get_P")); + Assert.True(verifier.HasLocalsInit("E.get_P2")); + } + + [Fact] + public void SkipLocalsInit_02() + { + string source = """ +static unsafe class E +{ + extension(int i) + { + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public int M() { int j = 0; return j; } + + public int M2() { int j = 0; return j; } + } +} +"""; + + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped); + Assert.False(verifier.HasLocalsInit("E.M")); + Assert.True(verifier.HasLocalsInit("E.M2")); + } } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs index 71221f426dc0b..700958410f360 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs @@ -100,6 +100,11 @@ internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) return true; } + internal override int TryGetOverloadResolutionPriority() + { + return _underlyingMethod.TryGetOverloadResolutionPriority(); + } + internal sealed override bool HasAsyncMethodBuilderAttribute(out TypeSymbol? builderArgument) { builderArgument = null; From c195a1fde1f13954b8af9a674cf5fce9702b2691 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Mon, 28 Apr 2025 08:19:39 -0700 Subject: [PATCH 072/114] For IsAtDotDotToken, ensure the current token is a DotToken before parsing the next token to see if it's also a DotToken. (#78338) I see LanguageParser.IsAtDotToken accounting for about 1 second of CPU time (0.5%) in the C# editing speedometer test. Local testing indicates about 85% of calls into this method will be filtered by this check. Test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/631973 See PR for details on the perf characteristics with and without the change. --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 692740c6eaa52..188f5707b52e5 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -11477,7 +11477,13 @@ private AssignmentExpressionSyntax ParseAssignmentExpression(SyntaxKind operator /// Check if we're currently at a .. sequence that can then be parsed out as a . public bool IsAtDotDotToken() - => IsAtDotDotToken(this.CurrentToken, this.PeekToken(1)); + { + if (this.CurrentToken.Kind != SyntaxKind.DotToken) + return false; + + var nextToken = this.PeekToken(1); + return nextToken.Kind == SyntaxKind.DotToken && NoTriviaBetween(this.CurrentToken, nextToken); + } public static bool IsAtDotDotToken(SyntaxToken token1, SyntaxToken token2) => token1.Kind == SyntaxKind.DotToken && From b01529a325b80ab0cd17f77bf1d16ac512762b9b Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Mon, 28 Apr 2025 09:45:33 -0700 Subject: [PATCH 073/114] Refactor/cleanup usages of MakeCallWithNoExplicitArgument helper (#78273) --- .../LocalRewriter_ForEachStatement.cs | 24 ++++--------------- .../LocalRewriter_UsingStatement.cs | 4 ++-- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs index 2b7c275bcc0a6..fc099b36f20b7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs @@ -180,8 +180,8 @@ private BoundStatement RewriteForEachEnumerator( } // ((C)(x)).GetEnumerator(); OR (x).GetEnumerator(); OR async variants (which fill-in arguments for optional parameters) - BoundExpression enumeratorVarInitValue = SynthesizeCall(getEnumeratorInfo, forEachSyntax, receiver, - allowExtensionAndOptionalParameters: isAsync || getEnumeratorInfo.Method.IsExtensionMethod || getEnumeratorInfo.Method.GetIsNewExtensionMember(), firstRewrittenArgument: firstRewrittenArgument); + BoundExpression enumeratorVarInitValue = MakeCall(getEnumeratorInfo, forEachSyntax, receiver, + firstRewrittenArgument: firstRewrittenArgument); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); @@ -214,11 +214,10 @@ private BoundStatement RewriteForEachEnumerator( // } var rewrittenBodyBlock = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVarDecl, rewrittenBody, forEachSyntax); - BoundExpression rewrittenCondition = SynthesizeCall( + BoundExpression rewrittenCondition = MakeCall( methodArgumentInfo: enumeratorInfo.MoveNextInfo, syntax: forEachSyntax, - receiver: boundEnumeratorVar, - allowExtensionAndOptionalParameters: isAsync, + expression: boundEnumeratorVar, firstRewrittenArgument: null); var disposalFinallyBlock = GetDisposalFinallyBlock(forEachSyntax, enumeratorInfo, enumeratorType, boundEnumeratorVar, out var hasAsyncDisposal); @@ -369,7 +368,7 @@ private bool TryGetDisposeMethod(SyntaxNode forEachSyntax, ForEachEnumeratorInfo } // ((IDisposable)e).Dispose() or e.Dispose() or await ((IAsyncDisposable)e).DisposeAsync() or await e.DisposeAsync() - disposeCall = MakeCallWithNoExplicitArgument(disposeInfo, forEachSyntax, receiver, firstRewrittenArgument: null); + disposeCall = MakeCall(disposeInfo, forEachSyntax, receiver, firstRewrittenArgument: null); BoundStatement disposeCallStatement; var disposeAwaitableInfoOpt = enumeratorInfo.DisposeAwaitableInfo; @@ -535,19 +534,6 @@ private BoundExpression ConvertReceiverForInvocation(CSharpSyntaxNode syntax, Bo return receiver; } - private BoundExpression SynthesizeCall(MethodArgumentInfo methodArgumentInfo, CSharpSyntaxNode syntax, BoundExpression? receiver, bool allowExtensionAndOptionalParameters, BoundExpression? firstRewrittenArgument) - { - if (allowExtensionAndOptionalParameters) - { - // Generate a call with zero explicit arguments, but with implicit arguments for optional and params parameters. - return MakeCallWithNoExplicitArgument(methodArgumentInfo, syntax, receiver, firstRewrittenArgument: firstRewrittenArgument); - } - - // Generate a call with literally zero arguments - Debug.Assert(methodArgumentInfo.Arguments.IsEmpty); - return BoundCall.Synthesized(syntax, receiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, methodArgumentInfo.Method, arguments: ImmutableArray.Empty); - } - /// /// Lower a foreach loop that will enumerate a collection via indexing. /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index a7dcc68c4d583..98ae93ea8ac69 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -470,7 +470,7 @@ private BoundExpression GenerateDisposeCall( disposeInfo = MethodArgumentInfo.CreateParameterlessMethod(disposeMethod); } - disposeCall = MakeCallWithNoExplicitArgument(disposeInfo, resourceSyntax, disposedExpression, firstRewrittenArgument: null); + disposeCall = MakeCall(disposeInfo, resourceSyntax, disposedExpression, firstRewrittenArgument: null); if (awaitOpt is object) { @@ -489,7 +489,7 @@ private BoundExpression GenerateDisposeCall( /// Synthesize a call `expression.Method()`, but with some extra smarts to handle extension methods. This call expects that the /// receiver parameter has already been visited. /// - private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo methodArgumentInfo, SyntaxNode syntax, BoundExpression? expression, BoundExpression? firstRewrittenArgument) + private BoundExpression MakeCall(MethodArgumentInfo methodArgumentInfo, SyntaxNode syntax, BoundExpression? expression, BoundExpression? firstRewrittenArgument) { MethodSymbol method = methodArgumentInfo.Method; From 866ed5430385287f77e738c5a3fba0101ca4ff1e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:08:58 -0700 Subject: [PATCH 074/114] Simplify tests --- .../ImplementInterfaceTests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs index 1263931a130ca..4fd517f3d5af1 100644 --- a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs @@ -566,7 +566,6 @@ public void M(string? s1, string s2) }, }, CodeActionEquivalenceKey = "False;False;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }; test.Options.AddRange(AllOptionsOff); @@ -2303,7 +2302,6 @@ public void Method1() Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); await new VerifyCS.Test @@ -7219,7 +7217,6 @@ public void set_P(int x, object Value) }, }, CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -7288,7 +7285,6 @@ public string this[int i] }, }, CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }; test.Options.AddRange(AllOptionsOff); @@ -8732,7 +8728,6 @@ void IInterface.M1() DiagnosticSelector = diagnostics => diagnostics[1], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -8896,7 +8891,6 @@ int IInterface.P2 }, Options = { AllOptionsOff }, CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11095,7 +11089,6 @@ public static void M1() """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11204,7 +11197,6 @@ class C : ITest """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11239,7 +11231,6 @@ class C : ITest """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11281,7 +11272,6 @@ class C : ITest """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11316,7 +11306,6 @@ class C : ITest """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -11458,8 +11447,6 @@ public static int M(ITest x) """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, - }.RunAsync(); } @@ -11494,8 +11481,6 @@ public static int M(C x) """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, - }.RunAsync(); } @@ -11719,7 +11704,6 @@ public static explicit operator string(C3 x) """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), CodeActionEquivalenceKey = "False;False;True:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } From 8f36433e6409cfbbf2f439db73d8fe734088449e Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Mon, 28 Apr 2025 10:17:23 -0700 Subject: [PATCH 075/114] Allow batch updating of ProjectUpdateState's RemovedMetadataReferences and AddedMetadataReferences members ProjectSystemProject.UpdateMetadataReferences was previously updating the added/removed metadata references in ProjectUpdateState one at a time. The WithIncrementalMetadataReferences(Added/Removed) code would allocate a new ImmutableArray on each invocation. Instead, we can batch up those changes. With this change, allocations under UpdateMetdataReferences went from 87 MB (0.7%) of allocations during solution load to 30 MB (0.3%) Test insertion PR: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/631996 See PR for details about before/after allocation patterns. --- .../Workspace/ProjectSystem/ProjectSystemProject.cs | 12 ++++++++++-- ...ProjectSystemProjectFactory.ProjectUpdateState.cs | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index b04d9bb534a95..4ac028169b435 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -687,6 +687,8 @@ static ProjectUpdateState UpdateMetadataReferences( List<(string path, MetadataReferenceProperties properties)> metadataReferencesAddedInBatch) { var projectId = projectBeforeMutation.Id; + using var _1 = ArrayBuilder.GetInstance(out var peReferencesRemoved); + using var _2 = ArrayBuilder.GetInstance(out var peReferencesAdded); // Metadata reference removing. Do this before adding in case this removes a project reference that we are also // going to add in the same batch. This could happen if case is changing, or we're targeting a different output @@ -706,13 +708,16 @@ static ProjectUpdateState UpdateMetadataReferences( .OfType() .Single(m => m.FilePath == path && m.Properties == properties); - projectUpdateState = projectUpdateState.WithIncrementalMetadataReferenceRemoved(metadataReference); + peReferencesRemoved.Add(metadataReference); solutionChanges.UpdateSolutionForProjectAction( projectId, solutionChanges.Solution.RemoveMetadataReference(projectId, metadataReference)); } } + if (peReferencesRemoved.Count > 0) + projectUpdateState = projectUpdateState.WithIncrementalMetadataReferencesRemoved(peReferencesRemoved); + // Metadata reference adding... if (metadataReferencesAddedInBatch.Count > 0) { @@ -730,10 +735,13 @@ static ProjectUpdateState UpdateMetadataReferences( else { var metadataReference = CreateMetadataReference_NoLock(path, properties, solutionChanges.Solution.Services); - projectUpdateState = projectUpdateState.WithIncrementalMetadataReferenceAdded(metadataReference); + peReferencesAdded.Add(metadataReference); } } + if (peReferencesAdded.Count > 0) + projectUpdateState = projectUpdateState.WithIncrementalMetadataReferencesAdded(peReferencesAdded); + solutionChanges.UpdateSolutionForProjectAction( projectId, solutionChanges.Solution diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs index b77f38e86c83c..034b8876289cd 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; @@ -117,9 +118,15 @@ static ImmutableDictionary> RemoveProject(stri public ProjectUpdateState WithIncrementalMetadataReferenceRemoved(PortableExecutableReference reference) => this with { RemovedMetadataReferences = RemovedMetadataReferences.Add(reference) }; + public ProjectUpdateState WithIncrementalMetadataReferencesRemoved(ArrayBuilder references) + => this with { RemovedMetadataReferences = RemovedMetadataReferences.AddRange(references) }; + public ProjectUpdateState WithIncrementalMetadataReferenceAdded(PortableExecutableReference reference) => this with { AddedMetadataReferences = AddedMetadataReferences.Add(reference) }; + public ProjectUpdateState WithIncrementalMetadataReferencesAdded(ArrayBuilder references) + => this with { AddedMetadataReferences = AddedMetadataReferences.AddRange(references) }; + public ProjectUpdateState WithIncrementalAnalyzerReferenceRemoved(string reference) => this with { RemovedAnalyzerReferences = RemovedAnalyzerReferences.Add(reference) }; From d6866c2eaa59d3854ce7abb67a4da4a4e56c9066 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:20:28 -0700 Subject: [PATCH 076/114] Simplify tests --- .../ImplementInterfaceTests.cs | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs index 4fd517f3d5af1..ea87fa902a534 100644 --- a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests.cs @@ -69,7 +69,6 @@ internal static async Task TestWithAllCodeStyleOptionsOffAsync( TestCode = initialMarkup, FixedCode = expectedMarkup, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = codeAction?.equivalenceKey, CodeActionIndex = codeAction?.index, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -108,7 +107,6 @@ private static async Task TestInRegularAndScriptAsync( { TestCode = initialMarkup, FixedCode = expectedMarkup, - CodeActionEquivalenceKey = codeAction?.equivalenceKey, CodeActionIndex = codeAction?.index, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -565,7 +563,6 @@ public void M(string? s1, string s2) """, }, }, - CodeActionEquivalenceKey = "False;False;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }; test.Options.AddRange(AllOptionsOff); @@ -1251,7 +1248,6 @@ class D : {|CS0535:I|} MarkupHandling = MarkupMode.Allow, }, CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", CodeActionIndex = 1, }; @@ -2301,7 +2297,6 @@ public void Method1() """, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), - CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); await new VerifyCS.Test @@ -2325,7 +2320,6 @@ public void Method1() """, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;i", CodeActionIndex = 1, }.RunAsync(); @@ -2350,7 +2344,6 @@ void I.Method1() """, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), - CodeActionEquivalenceKey = "True;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 2, }.RunAsync(); } @@ -2584,7 +2577,6 @@ public int M() CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), DiagnosticSelector = diagnostics => diagnostics[0], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", CodeActionIndex = 1, }.RunAsync(); @@ -2664,7 +2656,6 @@ public int M2() CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), DiagnosticSelector = diagnostics => diagnostics[1], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "False;False;False:global::I2;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", CodeActionIndex = 1, }.RunAsync(); } @@ -2728,7 +2719,6 @@ public int M() }, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(4, codeActions.Length), - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", CodeActionIndex = 1, }.RunAsync(); @@ -2788,7 +2778,6 @@ public int M() }, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(4, codeActions.Length), - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;aa", CodeActionIndex = 2, }.RunAsync(); } @@ -2880,7 +2869,6 @@ public int M() CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), DiagnosticSelector = diagnostics => diagnostics[0], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "False;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;a", CodeActionIndex = 1, }.RunAsync(); @@ -2968,7 +2956,6 @@ public int M2() CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), DiagnosticSelector = diagnostics => diagnostics[1], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "False;False;False:global::I2;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;b", CodeActionIndex = 1, }.RunAsync(); } @@ -3013,7 +3000,6 @@ public int M() """, Options = { AllOptionsOff }, CodeActionsVerifier = codeActions => Assert.Equal(3, codeActions.Length), - CodeActionEquivalenceKey = "False;False;False:global::IB;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;IA.B", CodeActionIndex = 1, }.RunAsync(); } @@ -6283,7 +6269,6 @@ public async Task TestImplementIDisposableExplicitly_NoNamespaceImportForSystem( {DisposePattern("protected virtual ", "C", "void System.IDisposable.", gcPrefix: "System.")} }} ", - CodeActionEquivalenceKey = "True;False;False:global::System.IDisposable;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", CodeActionIndex = 3, // 🐛 generated QualifiedName where SimpleMemberAccessExpression was expected @@ -7094,7 +7079,6 @@ partial class C : {|CS0535:I2|} }, Options = { AllOptionsOff }, CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "True;False;False:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -7153,7 +7137,6 @@ void IGoo.set_IndexProp(int p1, string Value) """, }, }, - CodeActionEquivalenceKey = "True;False;False:global::IGoo;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }; @@ -7216,7 +7199,6 @@ public void set_P(int x, object Value) """, }, }, - CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -7284,7 +7266,6 @@ public string this[int i] """, }, }, - CodeActionEquivalenceKey = "False;False;True:global::I;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }; test.Options.AddRange(AllOptionsOff); @@ -7475,7 +7456,6 @@ class Program : IDisposable { _options.FieldNamesAreCamelCaseWithUnderscorePrefix, }, - CodeActionEquivalenceKey = "False;False;True:global::System.IDisposable;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -8580,7 +8560,6 @@ void IInterface.Method1() } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -8619,7 +8598,6 @@ abstract class Class : IInterface } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "False;True;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -8727,7 +8705,6 @@ void IInterface.M1() Options = { AllOptionsOff }, DiagnosticSelector = diagnostics => diagnostics[1], CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -9016,7 +8993,6 @@ void IInterface.Method1() } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -9055,7 +9031,6 @@ abstract class Class : IInterface } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "False;True;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -9170,7 +9145,6 @@ void IInterface.Method1() MarkupHandling = MarkupMode.Allow, }, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -9782,7 +9756,6 @@ void IInterface.Method1() MarkupHandling = MarkupMode.Allow, }, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -9919,7 +9892,6 @@ void IInterface.Method1() } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -9958,7 +9930,6 @@ abstract class Class : IInterface } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "False;True;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -10048,7 +10019,6 @@ void IInterface.Method1() } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "True;False;False:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -10091,7 +10061,6 @@ abstract class Class : IInterface } """, Options = { AllOptionsOff }, - CodeActionEquivalenceKey = "False;True;True:global::IInterface;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -11088,7 +11057,6 @@ public static void M1() } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11124,7 +11092,6 @@ static void ITest.M1() } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -11161,7 +11128,6 @@ public static void M1() } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface_abstractly, codeAction.Title), - CodeActionEquivalenceKey = "False;True;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -11196,7 +11162,6 @@ class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11230,7 +11195,6 @@ class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11271,7 +11235,6 @@ class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11305,7 +11268,6 @@ class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11339,7 +11301,6 @@ class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -11374,7 +11335,6 @@ abstract class C : ITest } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface_abstractly, codeAction.Title), - CodeActionEquivalenceKey = "False;True;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); @@ -11410,7 +11370,6 @@ static int ITest.M(ITest x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); @@ -11446,7 +11405,6 @@ public static int M(ITest x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11480,7 +11438,6 @@ public static int M(C x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11514,7 +11471,6 @@ static int ITest.M(C x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); @@ -11550,7 +11506,6 @@ public static int M(C x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface_abstractly, codeAction.Title), - CodeActionEquivalenceKey = "False;True;True:global::ITest;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); @@ -11627,7 +11582,6 @@ static explicit I1.operator string(C3 x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), - CodeActionEquivalenceKey = "True;False;False:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -11703,7 +11657,6 @@ public static explicit operator string(C3 x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface, codeAction.Title), - CodeActionEquivalenceKey = "False;False;True:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", }.RunAsync(); } @@ -11778,7 +11731,6 @@ public static explicit operator string(C3 x) } """, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_interface_abstractly, codeAction.Title), - CodeActionEquivalenceKey = "False;True;True:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionIndex = 1, }.RunAsync(); } @@ -12049,7 +12001,6 @@ static explicit I11.operator int(C11 x) } """, CodeActionIndex = 1, - CodeActionEquivalenceKey = "True;False;False:global::I11;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", CodeActionVerifier = (codeAction, verifier) => verifier.Equal(CodeFixesResources.Implement_all_members_explicitly, codeAction.Title), }.RunAsync(); } From 65cc369b62990ba4a72c45cedb61d1e06766d167 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Mon, 28 Apr 2025 10:26:27 -0700 Subject: [PATCH 077/114] Implement SeparatedSyntaxList.Count(Func) (#78348) CSharpAttributeData.IsTargetEarlyAttribute was calling the Linq version of this list, and thus boxing the SeparatedSyntaxList enumerator, showing up as 5.9 MB (0.1%) of allocations in our OOP process during solution load. Not a huge win, but it's nice to add to the pit of success for using this type. Modifications to CSharpAttributeData started with a small cleanup to IsTargetEarlyAttribute to demonstrate the Count() usage. I then looked to see what other Linq usage was in the file, and it made sense to change those too. See PR for image with defails about allocations being addressed. --- .../Symbols/Attributes/AttributeData.cs | 14 +++++----- .../Syntax/SeparatedSyntaxListExtensions.cs | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 src/Compilers/Core/Portable/Syntax/SeparatedSyntaxListExtensions.cs diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/AttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/AttributeData.cs index 6c99ed0785ceb..3bb51945b48f3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/AttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/AttributeData.cs @@ -6,16 +6,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using System.Reflection; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using Microsoft.CodeAnalysis; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -90,7 +88,7 @@ internal static bool IsTargetEarlyAttribute(NamedTypeSymbol attributeType, Attri Debug.Assert(!attributeType.IsErrorType()); int argumentCount = (attributeSyntax.ArgumentList != null) ? - attributeSyntax.ArgumentList.Arguments.Count((arg) => arg.NameEquals == null) : + attributeSyntax.ArgumentList.Arguments.Count(static (arg) => arg.NameEquals == null) : 0; return AttributeData.IsTargetEarlyAttribute(attributeType, argumentCount, description); } @@ -138,7 +136,7 @@ internal bool IsSecurityAttribute(CSharpCompilation compilation) { string className = this.AttributeClass.ToDisplayString(SymbolDisplayFormat.TestFormat); - if (!this.CommonConstructorArguments.Any() & !this.CommonNamedArguments.Any()) + if (this.CommonConstructorArguments.IsEmpty && this.CommonNamedArguments.IsEmpty) { return className; } @@ -343,7 +341,7 @@ private DeclarativeSecurityAction DecodeSecurityAttributeAction(Symbol targetSym Debug.Assert(this.IsSecurityAttribute(compilation)); var ctorArgs = this.CommonConstructorArguments; - if (!ctorArgs.Any()) + if (ctorArgs.IsEmpty) { // NOTE: Security custom attributes must have a valid SecurityAction as its first argument, we have none here. // NOTE: Ideally, we should always generate 'CS7048: First argument to a security attribute must be a valid SecurityAction' for this case. @@ -365,7 +363,7 @@ private DeclarativeSecurityAction DecodeSecurityAttributeAction(Symbol targetSym } else { - TypedConstant firstArg = ctorArgs.First(); + TypedConstant firstArg = ctorArgs[0]; var firstArgType = (TypeSymbol?)firstArg.TypeInternal; if (firstArgType is object && firstArgType.Equals(compilation.GetWellKnownType(WellKnownType.System_Security_Permissions_SecurityAction))) { diff --git a/src/Compilers/Core/Portable/Syntax/SeparatedSyntaxListExtensions.cs b/src/Compilers/Core/Portable/Syntax/SeparatedSyntaxListExtensions.cs new file mode 100644 index 0000000000000..695e8ffda391a --- /dev/null +++ b/src/Compilers/Core/Portable/Syntax/SeparatedSyntaxListExtensions.cs @@ -0,0 +1,27 @@ +// 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; + +namespace Microsoft.CodeAnalysis +{ + internal static class SeparatedSyntaxListExtensions + { + internal static int Count(this SeparatedSyntaxList list, Func predicate) + where TNode : SyntaxNode + { + int n = list.Count; + int count = 0; + for (int i = 0; i < n; i++) + { + if (predicate(list[i])) + { + count++; + } + } + + return count; + } + } +} From ddf06dac04d211151cc8aa2838dac5f1f60ad5c2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:29:03 -0700 Subject: [PATCH 078/114] More cases --- .../ImplementInterfaceTests_FixAllTests.cs | 2 -- .../UsePrimaryConstructorTests.cs | 1 - .../GenerateEqualsAndGetHashCodeFromMembersTests.cs | 6 ------ .../InitializeParameter/AddParameterCheckTests.cs | 1 - .../ExtractMethod/ExtractLocalFunctionTests.cs | 4 ---- .../GenerateComparisonOperatorsTests.cs | 1 - .../IntroduceParameter/IntroduceParameterTests.vb | 11 ----------- .../MissingDiagnosticAnalyzerAttributeRuleTests.cs | 1 - 8 files changed, 27 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs index 50571d56bdbbc..5ae2f82b98863 100644 --- a/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs +++ b/src/Analyzers/CSharp/Tests/ImplementInterface/ImplementInterfaceTests_FixAllTests.cs @@ -158,7 +158,6 @@ class C2 : {|CS0535:I1|}, {|CS0535:I2|} }, CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInProjectCheck | CodeFixTestBehaviors.SkipFixAllInSolutionCheck, CodeActionEquivalenceKey = "False;False;True:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } @@ -310,7 +309,6 @@ public void F1() }, CodeFixTestBehaviors = CodeFixTestBehaviors.FixOne | CodeFixTestBehaviors.SkipFixAllInDocumentCheck | CodeFixTestBehaviors.SkipFixAllInSolutionCheck, CodeActionEquivalenceKey = "False;False;True:global::I1;Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction;", - CodeActionIndex = 0, }.RunAsync(); } diff --git a/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs b/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs index 4c6a93afc67c5..238322577e0f9 100644 --- a/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs +++ b/src/Analyzers/CSharp/Tests/UsePrimaryConstructor/UsePrimaryConstructorTests.cs @@ -4096,7 +4096,6 @@ class C() { } """, - CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs index 0f0ceccd2f1c5..fbda58a3296d7 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersTests.cs @@ -544,7 +544,6 @@ public override bool Equals(object obj) { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, Options = { PreferImplicitTypeWithInfo() }, }.RunAsync(); @@ -1333,7 +1332,6 @@ public override bool Equals(object obj) { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, CodeActionEquivalenceKey = FeaturesResources.Generate_Equals_object, CodeActionVerifier = (codeAction, verifier) => verifier.Equal(FeaturesResources.Generate_Equals_object, codeAction.Title), }.RunAsync(); @@ -1490,7 +1488,6 @@ public override bool Equals(object obj) { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, Options = { PreferImplicitTypeWithInfo() }, }.RunAsync(); @@ -2540,7 +2537,6 @@ enum Bar { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, Options = { PreferImplicitTypeWithInfo() }, }.RunAsync(); @@ -2614,7 +2610,6 @@ struct Bar : IEquatable { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, LanguageVersion = LanguageVersion.CSharp6, Options = { PreferImplicitTypeWithInfo() }, }.RunAsync(); @@ -3949,7 +3944,6 @@ public override bool Equals(object obj) { TestCode = code, FixedCode = fixedCode, - CodeActionIndex = 0, }.RunAsync(); } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs index 46ef3265d65b2..612f249da0c30 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs @@ -756,7 +756,6 @@ public C(string a, bool b, string c) } } """, - CodeActionIndex = 0, CodeActionEquivalenceKey = nameof(FeaturesResources.Add_null_checks_for_all_parameters) }.RunAsync(); } diff --git a/src/Features/CSharpTest/ExtractMethod/ExtractLocalFunctionTests.cs b/src/Features/CSharpTest/ExtractMethod/ExtractLocalFunctionTests.cs index c1f51cb00c7a3..0ae7a1e3a0f34 100644 --- a/src/Features/CSharpTest/ExtractMethod/ExtractLocalFunctionTests.cs +++ b/src/Features/CSharpTest/ExtractMethod/ExtractLocalFunctionTests.cs @@ -5193,7 +5193,6 @@ static void NewMethod() }, FixedCode = expected, LanguageVersion = LanguageVersion.CSharp9, - CodeActionIndex = 0, CodeActionEquivalenceKey = nameof(FeaturesResources.Extract_local_function), }.RunAsync(); } @@ -5234,7 +5233,6 @@ static int NewMethod() }, FixedCode = expected, LanguageVersion = LanguageVersion.CSharp9, - CodeActionIndex = 0, CodeActionEquivalenceKey = nameof(FeaturesResources.Extract_local_function), }.RunAsync(); } @@ -5283,7 +5281,6 @@ class Ignored { } }, FixedCode = expected, LanguageVersion = LanguageVersion.CSharp9, - CodeActionIndex = 0, CodeActionEquivalenceKey = nameof(FeaturesResources.Extract_local_function), }.RunAsync(); } @@ -5336,7 +5333,6 @@ class Ignored2 { } }, FixedCode = expected, LanguageVersion = LanguageVersion.CSharp9, - CodeActionIndex = 0, CodeActionEquivalenceKey = nameof(FeaturesResources.Extract_local_function), }.RunAsync(); } diff --git a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs index 20141988dfa3e..d78310058d78e 100644 --- a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs +++ b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs @@ -434,7 +434,6 @@ class C : IComparable, IComparable { TestCode = code, FixedCode = GetFixedCode("C"), - CodeActionIndex = 0, CodeActionEquivalenceKey = "Generate_for_0_C", }.RunAsync(); diff --git a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb index 634897519851b..5083ecac0d733 100644 --- a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb +++ b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb @@ -31,7 +31,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -76,7 +75,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -105,7 +103,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -166,7 +163,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -611,7 +607,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -635,7 +630,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -678,7 +672,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -708,7 +701,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -848,7 +840,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -982,7 +973,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function @@ -1014,7 +1004,6 @@ End Class" { .TestCode = source, .FixedCode = expected, - .CodeActionIndex = 0 }.RunAsync() End Function diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs index 4081a9f6d0b62..f95ce025298e3 100644 --- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs +++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs @@ -78,7 +78,6 @@ public override void Initialize(AnalysisContext context) ExpectedDiagnostics = { expected }, }, FixedState = { Sources = { fixedCode_WithCSharpAttribute } }, - CodeActionIndex = 0, CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.CSharp), }.RunAsync(); From 0d43d3de4696d38a966221caac59b13b63601872 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:29:37 -0700 Subject: [PATCH 079/114] Raw strings --- .../GenerateComparisonOperatorsTests.cs | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs index d78310058d78e..9d79e97e583a3 100644 --- a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs +++ b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs @@ -402,33 +402,35 @@ class C : IComparable, IComparable } """; static string GetFixedCode(string type) -=> $@"using System; - -class C : IComparable, IComparable -{{ - public int CompareTo(C c) => 0; - public int CompareTo(int c) => 0; - - public static bool operator <(C left, {type} right) - {{ - return left.CompareTo(right) < 0; - }} - - public static bool operator >(C left, {type} right) - {{ - return left.CompareTo(right) > 0; - }} - - public static bool operator <=(C left, {type} right) - {{ - return left.CompareTo(right) <= 0; - }} - - public static bool operator >=(C left, {type} right) - {{ - return left.CompareTo(right) >= 0; - }} -}}"; + => $$""" + using System; + + class C : IComparable, IComparable + { + public int CompareTo(C c) => 0; + public int CompareTo(int c) => 0; + + public static bool operator <(C left, {{type}} right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator >(C left, {{type}} right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator <=(C left, {{type}} right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >=(C left, {{type}} right) + { + return left.CompareTo(right) >= 0; + } + } + """; await new VerifyCS.Test { From 440468219fc0612816f2850c63383b09407c0160 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:30:32 -0700 Subject: [PATCH 080/114] Simplify --- ...ingDiagnosticAnalyzerAttributeRuleTests.cs | 159 +++++++++--------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs index f95ce025298e3..35b5ccf8ada4d 100644 --- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs +++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs @@ -7,23 +7,23 @@ using System.Threading.Tasks; using Analyzer.Utilities; using Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers; +using Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers.Fixers; using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.VisualBasic.Analyzers.MetaAnalyzers.CodeFixes; +using Test.Utilities; using Xunit; -using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.DiagnosticAnalyzerAttributeAnalyzer, - Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers.Fixers.CSharpApplyDiagnosticAnalyzerAttributeFix>; -using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.DiagnosticAnalyzerAttributeAnalyzer, - Microsoft.CodeAnalysis.VisualBasic.Analyzers.MetaAnalyzers.CodeFixes.BasicApplyDiagnosticAnalyzerAttributeFix>; - -namespace Microsoft.CodeAnalysis.Analyzers.UnitTests.MetaAnalyzers + +namespace Microsoft.CodeAnalysis.Analyzers.UnitTests.MetaAnalyzers; + +using VerifyCS = CSharpCodeFixVerifier; +using VerifyVB = VisualBasicCodeFixVerifier; + +public class MissingDiagnosticAnalyzerAttributeRuleTests { - public class MissingDiagnosticAnalyzerAttributeRuleTests + [Fact] + public async Task CSharp_VerifyDiagnosticAndFixesAsync() { - [Fact] - public async Task CSharp_VerifyDiagnosticAndFixesAsync() - { - var source = @" + var source = @" using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; @@ -44,11 +44,11 @@ public override void Initialize(AnalysisContext context) } }"; #pragma warning disable RS0030 // Do not use banned APIs - DiagnosticResult expected = VerifyCS.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); + DiagnosticResult expected = VerifyCS.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); #pragma warning restore RS0030 // Do not use banned APIs - await VerifyCS.VerifyAnalyzerAsync(source, expected); + await VerifyCS.VerifyAnalyzerAsync(source, expected); - var fixedCode_WithCSharpAttribute = @" + var fixedCode_WithCSharpAttribute = @" using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; @@ -70,18 +70,18 @@ public override void Initialize(AnalysisContext context) } }"; - await new VerifyCS.Test + await new VerifyCS.Test + { + TestState = { - TestState = - { - Sources = { source }, - ExpectedDiagnostics = { expected }, - }, - FixedState = { Sources = { fixedCode_WithCSharpAttribute } }, - CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.CSharp), - }.RunAsync(); - - var fixedCode_WithCSharpAndVBAttributes = @" + Sources = { source }, + ExpectedDiagnostics = { expected }, + }, + FixedState = { Sources = { fixedCode_WithCSharpAttribute } }, + CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.CSharp), + }.RunAsync(); + + var fixedCode_WithCSharpAndVBAttributes = @" using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; @@ -103,23 +103,23 @@ public override void Initialize(AnalysisContext context) } }"; - await new VerifyCS.Test + await new VerifyCS.Test + { + TestState = { - TestState = - { - Sources = { source }, - ExpectedDiagnostics = { expected }, - }, - FixedState = { Sources = { fixedCode_WithCSharpAndVBAttributes } }, - CodeActionIndex = 2, - CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_2, LanguageNames.CSharp, LanguageNames.VisualBasic), - }.RunAsync(); - } + Sources = { source }, + ExpectedDiagnostics = { expected }, + }, + FixedState = { Sources = { fixedCode_WithCSharpAndVBAttributes } }, + CodeActionIndex = 2, + CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_2, LanguageNames.CSharp, LanguageNames.VisualBasic), + }.RunAsync(); + } - [Fact] - public async Task VisualBasic_VerifyDiagnosticAndFixesAsync() - { - var source = @" + [Fact] + public async Task VisualBasic_VerifyDiagnosticAndFixesAsync() + { + var source = @" Imports System Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis @@ -138,11 +138,11 @@ End Sub End Class "; #pragma warning disable RS0030 // Do not use banned APIs - DiagnosticResult expected = VerifyVB.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); + DiagnosticResult expected = VerifyVB.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); #pragma warning restore RS0030 // Do not use banned APIs - await VerifyVB.VerifyAnalyzerAsync(source, expected); + await VerifyVB.VerifyAnalyzerAsync(source, expected); - var fixedCode_WithVBAttribute = @" + var fixedCode_WithVBAttribute = @" Imports System Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis @@ -162,19 +162,19 @@ End Sub End Class "; - await new VerifyVB.Test + await new VerifyVB.Test + { + TestState = { - TestState = - { - Sources = { source }, - ExpectedDiagnostics = { expected }, - }, - FixedState = { Sources = { fixedCode_WithVBAttribute } }, - CodeActionIndex = 1, - CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.VisualBasic), - }.RunAsync(); - - var fixedCode_WithCSharpAndVBAttributes = @" + Sources = { source }, + ExpectedDiagnostics = { expected }, + }, + FixedState = { Sources = { fixedCode_WithVBAttribute } }, + CodeActionIndex = 1, + CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.VisualBasic), + }.RunAsync(); + + var fixedCode_WithCSharpAndVBAttributes = @" Imports System Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis @@ -194,23 +194,23 @@ End Sub End Class "; - await new VerifyVB.Test + await new VerifyVB.Test + { + TestState = { - TestState = - { - Sources = { source }, - ExpectedDiagnostics = { expected }, - }, - FixedState = { Sources = { fixedCode_WithCSharpAndVBAttributes } }, - CodeActionIndex = 2, - CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_2, LanguageNames.CSharp, LanguageNames.VisualBasic), - }.RunAsync(); - } + Sources = { source }, + ExpectedDiagnostics = { expected }, + }, + FixedState = { Sources = { fixedCode_WithCSharpAndVBAttributes } }, + CodeActionIndex = 2, + CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_2, LanguageNames.CSharp, LanguageNames.VisualBasic), + }.RunAsync(); + } - [Fact] - public async Task CSharp_NoDiagnosticCasesAsync() - { - var source = @" + [Fact] + public async Task CSharp_NoDiagnosticCasesAsync() + { + var source = @" using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; @@ -236,13 +236,13 @@ public abstract class MyAbstractAnalyzerWithoutAttribute : DiagnosticAnalyzer { } "; - await VerifyCS.VerifyAnalyzerAsync(source); - } + await VerifyCS.VerifyAnalyzerAsync(source); + } - [Fact] - public async Task VisualBasic_NoDiagnosticCasesAsync() - { - var source = @" + [Fact] + public async Task VisualBasic_NoDiagnosticCasesAsync() + { + var source = @" Imports System Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis @@ -265,7 +265,6 @@ Public MustInherit Class MyAbstractAnalyzerWithoutAttribute Inherits DiagnosticAnalyzer End Class "; - await VerifyVB.VerifyAnalyzerAsync(source); - } + await VerifyVB.VerifyAnalyzerAsync(source); } } From 7d726850a2e7aa24c43aa1e22b97f656232c5083 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 10:32:05 -0700 Subject: [PATCH 081/114] Simplify --- .../ConflictMarkerResolutionTests.cs | 15 - .../ConflictMarkerResolutionTests.vb | 4 - ...ingDiagnosticAnalyzerAttributeRuleTests.cs | 337 +++++++++--------- 3 files changed, 170 insertions(+), 186 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.cs b/src/Analyzers/CSharp/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.cs index d7d24ecec7bcb..1080fae583157 100644 --- a/src/Analyzers/CSharp/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.cs +++ b/src/Analyzers/CSharp/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.cs @@ -68,7 +68,6 @@ static void Main(string[] args) TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -225,7 +224,6 @@ namespace N TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -319,7 +317,6 @@ static void Main(string[] args) TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -417,7 +414,6 @@ static void Main(string[] args) TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -590,7 +586,6 @@ public void M() TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -631,7 +626,6 @@ public void M() TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -665,7 +659,6 @@ void x() { TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -801,7 +794,6 @@ class Program3 TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 2, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -972,7 +964,6 @@ static void Main(string[] args) TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1135,7 +1126,6 @@ namespace N TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1233,7 +1223,6 @@ static void Main(string[] args) TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1320,7 +1309,6 @@ public void M() TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1363,7 +1351,6 @@ public void M() TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1399,7 +1386,6 @@ void x() { TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 1, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } @@ -1510,7 +1496,6 @@ class Program3 TestCode = source, FixedCode = fixedSource, NumberOfIncrementalIterations = 2, - CodeActionIndex = 0, CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey, }.RunAsync(); } diff --git a/src/Analyzers/VisualBasic/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.vb b/src/Analyzers/VisualBasic/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.vb index df9d59eb55571..9415a50723f83 100644 --- a/src/Analyzers/VisualBasic/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.vb +++ b/src/Analyzers/VisualBasic/Tests/ConflictMarkerResolution/ConflictMarkerResolutionTests.vb @@ -51,7 +51,6 @@ end namespace" .TestCode = source, .FixedCode = fixedSource, .NumberOfIncrementalIterations = 1, - .CodeActionIndex = 0, .CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey }.RunAsync() End Function @@ -191,7 +190,6 @@ end namespace" .TestCode = source, .FixedCode = fixedSource, .NumberOfIncrementalIterations = 2, - .CodeActionIndex = 0, .CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey }.RunAsync() End Function @@ -331,7 +329,6 @@ end namespace" .TestCode = source, .FixedCode = fixedSource, .NumberOfIncrementalIterations = 1, - .CodeActionIndex = 0, .CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey }.RunAsync() End Function @@ -483,7 +480,6 @@ end namespace" .TestCode = source, .FixedCode = fixedSource, .NumberOfIncrementalIterations = 2, - .CodeActionIndex = 0, .CodeActionEquivalenceKey = AbstractResolveConflictMarkerCodeFixProvider.TakeTopEquivalenceKey }.RunAsync() End Function diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs index 35b5ccf8ada4d..1dc7800910b9e 100644 --- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs +++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/MissingDiagnosticAnalyzerAttributeRuleTests.cs @@ -18,57 +18,59 @@ namespace Microsoft.CodeAnalysis.Analyzers.UnitTests.MetaAnalyzers; using VerifyCS = CSharpCodeFixVerifier; using VerifyVB = VisualBasicCodeFixVerifier; -public class MissingDiagnosticAnalyzerAttributeRuleTests +public sealed class MissingDiagnosticAnalyzerAttributeRuleTests { [Fact] public async Task CSharp_VerifyDiagnosticAndFixesAsync() { - var source = @" -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; + var source = """ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; -class MyAnalyzer : DiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics - { - get - { - throw new NotImplementedException(); - } - } - - public override void Initialize(AnalysisContext context) - { - } -}"; + class MyAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + { + get + { + throw new NotImplementedException(); + } + } + + public override void Initialize(AnalysisContext context) + { + } + } + """; #pragma warning disable RS0030 // Do not use banned APIs - DiagnosticResult expected = VerifyCS.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); + DiagnosticResult expected = VerifyCS.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(6, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); #pragma warning restore RS0030 // Do not use banned APIs await VerifyCS.VerifyAnalyzerAsync(source, expected); - var fixedCode_WithCSharpAttribute = @" -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; + var fixedCode_WithCSharpAttribute = """ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; -[DiagnosticAnalyzer(LanguageNames.CSharp)] -class MyAnalyzer : DiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics - { - get - { - throw new NotImplementedException(); - } - } - - public override void Initialize(AnalysisContext context) - { - } -}"; + [DiagnosticAnalyzer(LanguageNames.CSharp)] + class MyAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + { + get + { + throw new NotImplementedException(); + } + } + + public override void Initialize(AnalysisContext context) + { + } + } + """; await new VerifyCS.Test { @@ -81,27 +83,28 @@ public override void Initialize(AnalysisContext context) CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.CSharp), }.RunAsync(); - var fixedCode_WithCSharpAndVBAttributes = @" -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; + var fixedCode_WithCSharpAndVBAttributes = """ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; -[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] -class MyAnalyzer : DiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics - { - get - { - throw new NotImplementedException(); - } - } - - public override void Initialize(AnalysisContext context) - { - } -}"; + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + class MyAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + { + get + { + throw new NotImplementedException(); + } + } + + public override void Initialize(AnalysisContext context) + { + } + } + """; await new VerifyCS.Test { @@ -119,48 +122,48 @@ public override void Initialize(AnalysisContext context) [Fact] public async Task VisualBasic_VerifyDiagnosticAndFixesAsync() { - var source = @" -Imports System -Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics - -Class MyAnalyzer - Inherits DiagnosticAnalyzer - Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) - Get - Throw New NotImplementedException() - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class -"; + var source = """ + Imports System + Imports System.Collections.Immutable + Imports Microsoft.CodeAnalysis + Imports Microsoft.CodeAnalysis.Diagnostics + + Class MyAnalyzer + Inherits DiagnosticAnalyzer + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Throw New NotImplementedException() + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + End Sub + End Class + """; #pragma warning disable RS0030 // Do not use banned APIs - DiagnosticResult expected = VerifyVB.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(7, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); + DiagnosticResult expected = VerifyVB.Diagnostic(DiagnosticAnalyzerAttributeAnalyzer.MissingDiagnosticAnalyzerAttributeRule).WithLocation(6, 7).WithArguments(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); #pragma warning restore RS0030 // Do not use banned APIs await VerifyVB.VerifyAnalyzerAsync(source, expected); - var fixedCode_WithVBAttribute = @" -Imports System -Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics - - -Class MyAnalyzer - Inherits DiagnosticAnalyzer - Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) - Get - Throw New NotImplementedException() - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class -"; + var fixedCode_WithVBAttribute = """ + Imports System + Imports System.Collections.Immutable + Imports Microsoft.CodeAnalysis + Imports Microsoft.CodeAnalysis.Diagnostics + + + Class MyAnalyzer + Inherits DiagnosticAnalyzer + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Throw New NotImplementedException() + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + End Sub + End Class + """; await new VerifyVB.Test { @@ -174,25 +177,25 @@ End Class CodeActionEquivalenceKey = string.Format(CodeAnalysisDiagnosticsResources.ApplyDiagnosticAnalyzerAttribute_1, LanguageNames.VisualBasic), }.RunAsync(); - var fixedCode_WithCSharpAndVBAttributes = @" -Imports System -Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics - - -Class MyAnalyzer - Inherits DiagnosticAnalyzer - Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) - Get - Throw New NotImplementedException() - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class -"; + var fixedCode_WithCSharpAndVBAttributes = """ + Imports System + Imports System.Collections.Immutable + Imports Microsoft.CodeAnalysis + Imports Microsoft.CodeAnalysis.Diagnostics + + + Class MyAnalyzer + Inherits DiagnosticAnalyzer + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Throw New NotImplementedException() + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + End Sub + End Class + """; await new VerifyVB.Test { @@ -210,61 +213,61 @@ End Class [Fact] public async Task CSharp_NoDiagnosticCasesAsync() { - var source = @" -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -class MyAnalyzerWithLanguageSpecificAttribute : DiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics - { - get - { - throw new NotImplementedException(); - } - } - - public override void Initialize(AnalysisContext context) - { - } -} - -public abstract class MyAbstractAnalyzerWithoutAttribute : DiagnosticAnalyzer -{ -} -"; + var source = """ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + class MyAnalyzerWithLanguageSpecificAttribute : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + { + get + { + throw new NotImplementedException(); + } + } + + public override void Initialize(AnalysisContext context) + { + } + } + + public abstract class MyAbstractAnalyzerWithoutAttribute : DiagnosticAnalyzer + { + } + """; await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task VisualBasic_NoDiagnosticCasesAsync() { - var source = @" -Imports System -Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics - - -Class MyAnalyzerWithLanguageSpecificAttribute - Inherits DiagnosticAnalyzer - Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) - Get - Throw New NotImplementedException() - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class - -Public MustInherit Class MyAbstractAnalyzerWithoutAttribute - Inherits DiagnosticAnalyzer -End Class -"; + var source = """ + Imports System + Imports System.Collections.Immutable + Imports Microsoft.CodeAnalysis + Imports Microsoft.CodeAnalysis.Diagnostics + + + Class MyAnalyzerWithLanguageSpecificAttribute + Inherits DiagnosticAnalyzer + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Throw New NotImplementedException() + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + End Sub + End Class + + Public MustInherit Class MyAbstractAnalyzerWithoutAttribute + Inherits DiagnosticAnalyzer + End Class + """; await VerifyVB.VerifyAnalyzerAsync(source); } } From 4d2b61ef5404384f15ca77c52d3ac1fbd8d93abf Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 28 Apr 2025 10:37:07 -0700 Subject: [PATCH 082/114] Reenable source indexing Source index issue should be fixed in https://github.com/dotnet/roslyn/pull/78335 --- azure-pipelines-official.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 8007730d6e721..f4baa8afe2f78 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -101,7 +101,7 @@ variables: - ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}: - name: enableSourceIndex - value: false + value: true - name: VSCodeOptimizationDataRoot value: $(Pipeline.Workspace)/profilingInputs/merged mibc From c96422788a8a47fe41f8eb492a6ab500ddd99b2b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 11:24:13 -0700 Subject: [PATCH 083/114] Fix --- .../IntroduceParameterTests.vb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb index 5083ecac0d733..38b472581cd5c 100644 --- a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb +++ b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb @@ -30,7 +30,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -74,7 +74,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -102,7 +102,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -162,7 +162,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -606,7 +606,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -629,7 +629,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -671,7 +671,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -700,7 +700,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -839,7 +839,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -972,7 +972,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function @@ -1003,7 +1003,7 @@ End Class" Await New VerifyVB.Test With { .TestCode = source, - .FixedCode = expected, + .FixedCode = expected }.RunAsync() End Function From 0ec9acf69d2d04d2dc0a9315f27b7c594d307ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Mon, 28 Apr 2025 11:25:14 -0700 Subject: [PATCH 084/114] ReferenceEqualityComparer (#78350) --- .../Binder/Binder.CapturedParametersFinder.cs | 1 - .../Portable/Binder/LocalBinderFactory.cs | 1 - .../CSharp/Portable/CodeGen/Optimizer.cs | 1 - .../Portable/Compiler/MethodCompiler.cs | 1 - .../EditAndContinue/CSharpSymbolMatcher.cs | 1 - .../Portable/Emitter/Model/PEModuleBuilder.cs | 1 - .../Portable/Emitter/NoPia/EmbeddedType.cs | 1 - .../Emitter/NoPia/EmbeddedTypesManager.cs | 1 - .../Portable/FlowAnalysis/AbstractFlowPass.cs | 1 - .../Lowering/ExtensionMethodBodyRewriter.cs | 3 +- .../LocalRewriter/DelegateCacheRewriter.cs | 1 - .../Symbols/Compilation_UsedAssemblies.cs | 1 - .../Symbols/MemberSymbolExtensions.cs | 2 +- .../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 2 +- .../Source/SourceMemberContainerSymbol.cs | 1 - .../Source/SourceNamedTypeSymbol_Extension.cs | 2 +- .../Symbols/Source/SourceNamespaceSymbol.cs | 1 - .../Source/TypeParameterConstraintClause.cs | 1 - .../Symbols/SubstitutedNamedTypeSymbol.cs | 1 - .../Portable/Symbols/Symbol_Attributes.cs | 1 - .../Symbols/Tuples/TupleTypeSymbol.cs | 1 - .../CSharp/Portable/Symbols/TypeMap.cs | 1 - .../Test/Emit3/Semantics/OutVarTests.cs | 1 - .../Semantics/PatternMatchingTestBase.cs | 1 - .../Test/Semantic/Semantics/NameOfTests.cs | 5 +- .../Semantic/Semantics/NativeIntegerTests.cs | 1 - .../Symbols/Metadata/PE/LoadingProperties.cs | 3 +- .../Core/Portable/CodeGen/ILBuilder.cs | 2 - .../Core/Portable/CodeGen/ItemTokenMap.cs | 1 - .../Core/Portable/CodeGen/LocalSlotManager.cs | 1 - .../Core/Portable/Compilation/Compilation.cs | 1 - .../DiagnosticAnalyzer/AnalyzerDriver.cs | 1 - ...nalyzerManager.AnalyzerExecutionContext.cs | 2 +- .../Portable/Emit/CommonPEModuleBuilder.cs | 1 - .../EditAndContinue/DeltaMetadataWriter.cs | 1 - .../Emit/NoPia/EmbeddedTypesManager.cs | 1 - .../ReferenceEqualityComparer.cs | 72 ++++++++++++------- .../AssemblyIdentity.DisplayName.cs | 1 + .../Portable/NativePdbWriter/PdbWriter.cs | 1 - .../Portable/PEWriter/FullMetadataWriter.cs | 1 - .../Core/Portable/PEWriter/MetadataWriter.cs | 1 - .../SourceGeneration/IncrementalContexts.cs | 1 - .../Nodes/PredicateSyntaxStrategy.cs | 2 +- .../Nodes/SharedInputNodes.cs | 2 - src/Compilers/Test/Core/Assert/AssertXml.cs | 1 - .../Portable/Binding/Binder_Query.vb | 1 - .../Portable/Binding/MemberSemanticModel.vb | 1 - .../Portable/Compilation/MethodCompiler.vb | 1 - .../Compilation/TypeCompilationState.vb | 1 - .../VisualBasicSymbolMatcher.vb | 1 - .../Portable/Emit/NoPia/EmbeddedType.vb | 1 - .../Emit/NoPia/EmbeddedTypesManager.vb | 1 - .../Portable/Emit/PEModuleBuilder.vb | 1 - .../Portable/Emit/SymbolTranslator.vb | 1 - .../LambdaRewriter/LambdaRewriter.Analysis.vb | 1 - .../Lowering/LocalRewriter/LocalRewriter.vb | 1 - .../LocalRewriter_LocalDeclaration.vb | 1 - .../MethodToClassRewriter.vb | 1 - .../Operations/VisualBasicOperationFactory.vb | 1 - .../BlockContexts/CompilationUnitContext.vb | 1 - .../Portable/Semantics/OverloadResolution.vb | 1 - .../TypeInference/TypeArgumentInference.vb | 1 - .../Symbols/Source/SourceModuleSymbol.vb | 1 - .../Symbols/Tuples/TupleTypeSymbol.vb | 1 - .../SymbolsTests/InstantiatingGenerics.vb | 1 - .../Shared/Utilities/ClassificationTypeMap.cs | 1 - .../Protocol/LspServices/LspServices.cs | 1 - .../CommonObjectFormatter.Visitor.cs | 1 - .../AbstractExtensionManager.cs | 1 + .../Portable/Utilities/ReferenceHolder`1.cs | 5 +- ...olutionCompilationState.RootedSymbolSet.cs | 6 +- .../Solution/SolutionCompilationState.cs | 1 - .../Remote/TestSerializerService.cs | 1 - .../Core/Workspace/Mef/MefLanguageServices.cs | 1 - .../Workspace/Mef/MefWorkspaceServices.cs | 1 - 75 files changed, 66 insertions(+), 104 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.CapturedParametersFinder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.CapturedParametersFinder.cs index 424192bf625b3..8a67729e86398 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.CapturedParametersFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.CapturedParametersFinder.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs index 95cfcd7c2d5a0..5f0caa5bd3d5f 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs @@ -13,7 +13,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index c628a423e0faa..365cd44a08702 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.CodeGen { diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 0036b26480517..1c996f88d1366 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -22,7 +22,6 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index 1787cd613877e..7e7f5a4f29ad0 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Emit { diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index eec2ef91da292..19cb20430e9bf 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -19,7 +19,6 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Emit { diff --git a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs index bf57711ed9da2..ce4e0f0935b1f 100644 --- a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs +++ b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedType.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; #if !DEBUG using NamedTypeSymbolAdapter = Microsoft.CodeAnalysis.CSharp.Symbols.NamedTypeSymbol; diff --git a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs index e72fb3b30ef81..974e94d5dbcf1 100644 --- a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs +++ b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; using Microsoft.CodeAnalysis.Emit.NoPia; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; #if !DEBUG using SymbolAdapter = Microsoft.CodeAnalysis.CSharp.Symbol; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 43b5e7678aa68..701437c60e5a6 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Lowering/ExtensionMethodBodyRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/ExtensionMethodBodyRewriter.cs index 0aa84711e2dc9..806b65f34ee61 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ExtensionMethodBodyRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ExtensionMethodBodyRewriter.cs @@ -4,11 +4,10 @@ using System; using System.Collections.Immutable; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DelegateCacheRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DelegateCacheRewriter.cs index 6e1c31d953cdb..107852dcc16f3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DelegateCacheRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/DelegateCacheRewriter.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp; diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_UsedAssemblies.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_UsedAssemblies.cs index fae723141245f..79a13edf82f8f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_UsedAssemblies.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_UsedAssemblies.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 3101eaad63127..da652b2e03b80 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -159,7 +159,7 @@ internal static ImmutableArray GetTypeParametersIncludingEx // Since we're concatenating type parameters from the extension and from the method together // we need to control the ordinals that are used - ordinals = new Dictionary(Roslyn.Utilities.ReferenceEqualityComparer.Instance); + ordinals = new Dictionary(ReferenceEqualityComparer.Instance); for (int i = 0; i < originalTypeParameters.Length; i++) { ordinals.Add(originalTypeParameters[i], i); diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index d3873369f43d8..c55907af86677 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -456,7 +456,7 @@ static MethodSymbol getMarkerMethodSymbol(PENamedTypeSymbol @this, ExtensionInfo if (uncommon.LazyImplementationMap is null) { - Interlocked.CompareExchange(ref uncommon.LazyImplementationMap, new ConcurrentDictionary(Roslyn.Utilities.ReferenceEqualityComparer.Instance), null); + Interlocked.CompareExchange(ref uncommon.LazyImplementationMap, new ConcurrentDictionary(ReferenceEqualityComparer.Instance), null); } return uncommon.LazyImplementationMap.GetOrAdd(method, findCorrespondingExtensionImplementationMethod, this); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 06ae16c32ad19..a62a2f5e27ea8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs index 03182aa0738b8..241e3a99b7502 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs @@ -90,7 +90,7 @@ internal sealed override ParameterSymbol? ExtensionParameter if (_lazyExtensionInfo.LazyImplementationMap is null) { - var builder = ImmutableDictionary.CreateBuilder(Roslyn.Utilities.ReferenceEqualityComparer.Instance); + var builder = ImmutableDictionary.CreateBuilder(ReferenceEqualityComparer.Instance); builder.AddRange( containingType.GetMembersUnordered().OfType(). diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs index ee1bea85c7f90..4f2c775890bc6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/TypeParameterConstraintClause.cs b/src/Compilers/CSharp/Portable/Symbols/Source/TypeParameterConstraintClause.cs index 986c50a904636..4ac0b2658b0e4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/TypeParameterConstraintClause.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/TypeParameterConstraintClause.cs @@ -12,7 +12,6 @@ using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs index f83a89bd604e5..f3441ac5c3afd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedNamedTypeSymbol.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs index 0850691a84d82..ba230880ac8b7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs index 482a7885f8f36..bfa781d486327 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.RuntimeMembers; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs b/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs index e37396f1c9685..fc4fba8ca5448 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.Symbols { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/OutVarTests.cs index 5c1212d8e9755..8dfd5cbaacf8a 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/OutVarTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/OutVarTests.cs @@ -17,7 +17,6 @@ using Roslyn.Utilities; using Microsoft.CodeAnalysis.Diagnostics; using Basic.Reference.Assemblies; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTestBase.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTestBase.cs index d37a1bb88a5b7..d81086d5ab741 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTestBase.cs @@ -14,7 +14,6 @@ using Roslyn.Test.Utilities; using Xunit; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UnitTests diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs index 90b49b4e018c0..75839787c6ba1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -1082,7 +1083,7 @@ void verifySymbolInfo(CandidateReason reason, SymbolInfo symbolInfo) AssertEx.SetEqual( symbols.Select(s => s.GetPublicSymbol()), symbolInfo.CandidateSymbols, - Roslyn.Utilities.ReferenceEqualityComparer.Instance); + ReferenceEqualityComparer.Instance); } } @@ -1246,7 +1247,7 @@ void verifySymbolInfo(CandidateReason reason, SymbolInfo symbolInfo) AssertEx.SetEqual( symbols.Select(s => s.GetPublicSymbol()), symbolInfo.CandidateSymbols, - Roslyn.Utilities.ReferenceEqualityComparer.Instance); + ReferenceEqualityComparer.Instance); } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs index 2f54e0e1b624a..ab2498b030803 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs @@ -17,7 +17,6 @@ using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingProperties.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingProperties.cs index c9fa1e41a322c..d8e951aeb135d 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingProperties.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingProperties.cs @@ -5,12 +5,11 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs index c68b2619901fa..2a170dbc2ffa9 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs @@ -13,8 +13,6 @@ using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CodeGen { diff --git a/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs b/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs index 7d387f9def7ea..1205a2ae2afd5 100644 --- a/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs +++ b/src/Compilers/Core/Portable/CodeGen/ItemTokenMap.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CodeGen { diff --git a/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs b/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs index 7f02bdc0ce639..6ab3b15df4910 100644 --- a/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs +++ b/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.CodeGen { diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index 627e40d20e8f5..6c8edee429110 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -28,7 +28,6 @@ using Microsoft.CodeAnalysis.Symbols; using Microsoft.DiaSymReader; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 0528bc9c60098..86e1a04700331 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -19,7 +19,6 @@ using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Diagnostics { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerExecutionContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerExecutionContext.cs index d2d60be6a7c51..930c137243b95 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerExecutionContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerExecutionContext.cs @@ -22,7 +22,7 @@ private sealed class AnalyzerExecutionContext /// /// Cached mapping of localizable strings in this descriptor to any exceptions thrown while obtaining them. /// - private static ImmutableDictionary s_localizableStringToException = ImmutableDictionary.Empty.WithComparers(Roslyn.Utilities.ReferenceEqualityComparer.Instance); + private static ImmutableDictionary s_localizableStringToException = ImmutableDictionary.Empty.WithComparers(ReferenceEqualityComparer.Instance); private readonly DiagnosticAnalyzer _analyzer; private readonly object _gate; diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index 44b8c70265965..84be01ad50e1e 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Emit { diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 21ebe9f2bab02..797c54af87e97 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -18,7 +18,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Emit { diff --git a/src/Compilers/Core/Portable/Emit/NoPia/EmbeddedTypesManager.cs b/src/Compilers/Core/Portable/Emit/NoPia/EmbeddedTypesManager.cs index a048a1081f78b..31f4189b1c8d8 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/EmbeddedTypesManager.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/EmbeddedTypesManager.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Diagnostics; using Cci = Microsoft.Cci; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Emit.NoPia { diff --git a/src/Compilers/Core/Portable/InternalUtilities/ReferenceEqualityComparer.cs b/src/Compilers/Core/Portable/InternalUtilities/ReferenceEqualityComparer.cs index 6604c56a13186..8176987e9e9cd 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ReferenceEqualityComparer.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ReferenceEqualityComparer.cs @@ -2,35 +2,59 @@ // 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.Runtime.CompilerServices; -namespace Roslyn.Utilities +#if NET5_0_OR_GREATER + +#pragma warning disable RS0016 // Add public types and members to the declared API (this is a supporting forwarder for an internal polyfill API) +[assembly: TypeForwardedTo(typeof(System.Collections.Generic.ReferenceEqualityComparer))] +#pragma warning restore RS0016 // Add public types and members to the declared API + +#else + +namespace System.Collections.Generic; + +internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { + private ReferenceEqualityComparer() { } + + /// + /// Gets the singleton instance. + /// + public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); + + /// + /// Determines whether two object references refer to the same object instance. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// if both and refer to the same object instance + /// or if both are ; otherwise, . + /// + /// + /// This API is a wrapper around . + /// It is not necessarily equivalent to calling . + /// + public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); + /// - /// Compares objects based upon their reference identity. + /// Returns a hash code for the specified object. The returned hash code is based on the object + /// identity, not on the contents of the object. /// - internal class ReferenceEqualityComparer : IEqualityComparer + /// The object for which to retrieve the hash code. + /// A hash code for the identity of . + /// + /// This API is a wrapper around . + /// It is not necessarily equivalent to calling . + /// + public int GetHashCode(object? obj) { - public static readonly ReferenceEqualityComparer Instance = new(); - - private ReferenceEqualityComparer() - { - } - - bool IEqualityComparer.Equals(object? a, object? b) - { - return a == b; - } - - int IEqualityComparer.GetHashCode(object? a) - { - return ReferenceEqualityComparer.GetHashCode(a); - } - - public static int GetHashCode(object? a) - { - return RuntimeHelpers.GetHashCode(a); - } + // Depending on target framework, RuntimeHelpers.GetHashCode might not be annotated + // with the proper nullability attribute. We'll suppress any warning that might + // result. + return RuntimeHelpers.GetHashCode(obj!); } } + +#endif diff --git a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs index 7f42ad11929fc..a15d220f097ee 100644 --- a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs +++ b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs index 84073d32f22b1..ca3d00581a291 100644 --- a/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs @@ -19,7 +19,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.DiaSymReader; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.Cci { diff --git a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs index 56000b4237628..6a7b860967e6b 100644 --- a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Emit; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.Cci { diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index 04e2aa2848d2d..f50496068f91d 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -32,7 +32,6 @@ using Microsoft.CodeAnalysis.Symbols; using Microsoft.DiaSymReader; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.Cci { diff --git a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs index 47c3b08419549..285c8f01a9ce5 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.SourceGeneration; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; #pragma warning disable RSEXPERIMENTAL004 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs index e6fc120ee48bd..edeb4fedbf6cf 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/PredicateSyntaxStrategy.cs @@ -48,7 +48,7 @@ public Builder(PredicateSyntaxStrategy owner, object key, StateTableStore tab _name = name; _comparer = comparer; _key = key; - _filterTable = table.GetStateTableOrEmpty(_owner._filterKey).ToBuilder(stepName: null, trackIncrementalSteps, equalityComparer: Roslyn.Utilities.ReferenceEqualityComparer.Instance); + _filterTable = table.GetStateTableOrEmpty(_owner._filterKey).ToBuilder(stepName: null, trackIncrementalSteps, equalityComparer: ReferenceEqualityComparer.Instance); _transformTable = table.GetStateTableOrEmpty(_key).ToBuilder(_name, trackIncrementalSteps, _comparer); } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs index 889cf2a04a7f3..e08976cface3d 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Diagnostics; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis { diff --git a/src/Compilers/Test/Core/Assert/AssertXml.cs b/src/Compilers/Test/Core/Assert/AssertXml.cs index 295f63fda679f..3f2c327bc091f 100644 --- a/src/Compilers/Test/Core/Assert/AssertXml.cs +++ b/src/Compilers/Test/Core/Assert/AssertXml.cs @@ -15,7 +15,6 @@ using System.Xml.Linq; using Roslyn.Utilities; using Xunit; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Roslyn.Test.Utilities { diff --git a/src/Compilers/VisualBasic/Portable/Binding/Binder_Query.vb b/src/Compilers/VisualBasic/Portable/Binding/Binder_Query.vb index a38d8fa211902..35085bb74d994 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/Binder_Query.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/Binder_Query.vb @@ -9,7 +9,6 @@ Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Binding/MemberSemanticModel.vb b/src/Compilers/VisualBasic/Portable/Binding/MemberSemanticModel.vb index a69e95e38efb8..a0479330d35c7 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/MemberSemanticModel.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/MemberSemanticModel.vb @@ -12,7 +12,6 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports TypeKind = Microsoft.CodeAnalysis.TypeKind -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic ''' diff --git a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb index a05d444058945..f0ace4940ea73 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb @@ -16,7 +16,6 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Compilation/TypeCompilationState.vb b/src/Compilers/VisualBasic/Portable/Compilation/TypeCompilationState.vb index a33df32368601..d9dbe3130512d 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/TypeCompilationState.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/TypeCompilationState.vb @@ -10,7 +10,6 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index db9448ac0f6a1..6e53bb10ee2c2 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -12,7 +12,6 @@ Imports Microsoft.CodeAnalysis.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Friend NotInheritable Class VisualBasicSymbolMatcher diff --git a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb index ac71befad36a5..a8375a56817f3 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedType.vb @@ -5,7 +5,6 @@ Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer #If Not DEBUG Then Imports NamedTypeSymbolAdapter = Microsoft.CodeAnalysis.VisualBasic.Symbols.NamedTypeSymbol diff --git a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedTypesManager.vb b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedTypesManager.vb index 89226af1c1a81..a8e99e4c7ec58 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedTypesManager.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NoPia/EmbeddedTypesManager.vb @@ -7,7 +7,6 @@ Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports System.Collections.Concurrent Imports System.Threading -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer #If Not DEBUG Then Imports SymbolAdapter = Microsoft.CodeAnalysis.VisualBasic.Symbol diff --git a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index ea1f4b7b63046..488e727e9be53 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -11,7 +11,6 @@ Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.Emit diff --git a/src/Compilers/VisualBasic/Portable/Emit/SymbolTranslator.vb b/src/Compilers/VisualBasic/Portable/Emit/SymbolTranslator.vb index 74ce78a71a0d0..edbbbd19e3fc4 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/SymbolTranslator.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/SymbolTranslator.vb @@ -8,7 +8,6 @@ Imports System.Threading Imports Microsoft.Cci Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.Emit diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.vb b/src/Compilers/VisualBasic/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.vb index 8fd97aefb58ab..c100c030385d4 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.vb @@ -9,7 +9,6 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Utilities -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb index e6d337367a2a8..9af1c39240d31 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter.vb @@ -8,7 +8,6 @@ Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.vb b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.vb index 468312ae84005..5a8c8ea02c9b1 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.vb @@ -6,7 +6,6 @@ Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class LocalRewriter diff --git a/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb index f16da69619f38..a33a500a20beb 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/MethodToClassRewriter/MethodToClassRewriter.vb @@ -7,7 +7,6 @@ Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.CodeGen Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb index 89ce274086a30..e57cf9b726564 100644 --- a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb @@ -9,7 +9,6 @@ Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.Operations Partial Friend NotInheritable Class VisualBasicOperationFactory diff --git a/src/Compilers/VisualBasic/Portable/Parser/BlockContexts/CompilationUnitContext.vb b/src/Compilers/VisualBasic/Portable/Parser/BlockContexts/CompilationUnitContext.vb index 9246534cd0ce8..f0b5ea81afc0c 100644 --- a/src/Compilers/VisualBasic/Portable/Parser/BlockContexts/CompilationUnitContext.vb +++ b/src/Compilers/VisualBasic/Portable/Parser/BlockContexts/CompilationUnitContext.vb @@ -7,7 +7,6 @@ Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer '----------------------------------------------------------------------------- ' Contains the definition of the DeclarationContext '----------------------------------------------------------------------------- diff --git a/src/Compilers/VisualBasic/Portable/Semantics/OverloadResolution.vb b/src/Compilers/VisualBasic/Portable/Semantics/OverloadResolution.vb index 254d6b2603a58..37c1c80ac84dc 100644 --- a/src/Compilers/VisualBasic/Portable/Semantics/OverloadResolution.vb +++ b/src/Compilers/VisualBasic/Portable/Semantics/OverloadResolution.vb @@ -13,7 +13,6 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree Imports TypeKind = Microsoft.CodeAnalysis.TypeKind -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Semantics/TypeInference/TypeArgumentInference.vb b/src/Compilers/VisualBasic/Portable/Semantics/TypeInference/TypeArgumentInference.vb index 32fcec4e7e6e8..bd542c90bd633 100644 --- a/src/Compilers/VisualBasic/Portable/Semantics/TypeInference/TypeArgumentInference.vb +++ b/src/Compilers/VisualBasic/Portable/Semantics/TypeInference/TypeArgumentInference.vb @@ -6,7 +6,6 @@ Imports System.Collections.Immutable Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb index a30a836f3121b..e6c5dc802a024 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceModuleSymbol.vb @@ -12,7 +12,6 @@ Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Tuples/TupleTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Tuples/TupleTypeSymbol.vb index 8d7ccc0d74dfc..71724fdf75414 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Tuples/TupleTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Tuples/TupleTypeSymbol.vb @@ -10,7 +10,6 @@ Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.RuntimeMembers Imports Microsoft.CodeAnalysis.VisualBasic.Emit -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Friend NotInheritable Class TupleTypeSymbol diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/InstantiatingGenerics.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/InstantiatingGenerics.vb index 84ff1dbaab13f..f9f45a7296ef9 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/InstantiatingGenerics.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/InstantiatingGenerics.vb @@ -10,7 +10,6 @@ Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities -Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Symbols diff --git a/src/EditorFeatures/Core/Shared/Utilities/ClassificationTypeMap.cs b/src/EditorFeatures/Core/Shared/Utilities/ClassificationTypeMap.cs index fdc2524f47df4..335ed4455678b 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/ClassificationTypeMap.cs +++ b/src/EditorFeatures/Core/Shared/Utilities/ClassificationTypeMap.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Text.Classification; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; diff --git a/src/LanguageServer/Protocol/LspServices/LspServices.cs b/src/LanguageServer/Protocol/LspServices/LspServices.cs index b2417c69b7d6b..5a7d595258c73 100644 --- a/src/LanguageServer/Protocol/LspServices/LspServices.cs +++ b/src/LanguageServer/Protocol/LspServices/LspServices.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CommonLanguageServerProtocol.Framework; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.LanguageServer; diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs index 2303b85fedd59..e5e4890f52a0e 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs @@ -13,7 +13,6 @@ using System.Runtime.CompilerServices; using Microsoft.Cci; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis.Scripting.Hosting { diff --git a/src/Workspaces/Core/Portable/ExtensionManager/AbstractExtensionManager.cs b/src/Workspaces/Core/Portable/ExtensionManager/AbstractExtensionManager.cs index e915519a71ba4..66453a8d5a6ac 100644 --- a/src/Workspaces/Core/Portable/ExtensionManager/AbstractExtensionManager.cs +++ b/src/Workspaces/Core/Portable/ExtensionManager/AbstractExtensionManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Extensions; diff --git a/src/Workspaces/Core/Portable/Utilities/ReferenceHolder`1.cs b/src/Workspaces/Core/Portable/Utilities/ReferenceHolder`1.cs index 73710a5e3bd3f..bc3986fcbfbd4 100644 --- a/src/Workspaces/Core/Portable/Utilities/ReferenceHolder`1.cs +++ b/src/Workspaces/Core/Portable/Utilities/ReferenceHolder`1.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; namespace Roslyn.Utilities; @@ -38,7 +39,7 @@ public static ReferenceHolder Weak(T value) return Strong(value); } - return new ReferenceHolder(new WeakReference(value), ReferenceEqualityComparer.GetHashCode(value)); + return new ReferenceHolder(new WeakReference(value), ReferenceEqualityComparer.Instance.GetHashCode(value)); } public T? TryGetTarget() @@ -86,7 +87,7 @@ public override int GetHashCode() if (_weakReference is object) return _hashCode; - return ReferenceEqualityComparer.GetHashCode(_strongReference); + return ReferenceEqualityComparer.Instance.GetHashCode(_strongReference); } internal static class TestAccessor diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs index 84e7238c32d1a..e45ff74dca285 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis; @@ -80,7 +80,7 @@ public static RootedSymbolSet Create(Compilation compilation) if (symbol == null) continue; - secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), symbol, MetadataReferenceInfo.From(reference))); + secondarySymbols.Add((ReferenceEqualityComparer.Instance.GetHashCode(symbol), symbol, MetadataReferenceInfo.From(reference))); } // Sort all the secondary symbols by their hash. This will allow us to easily binary search for them @@ -116,7 +116,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( { var secondarySymbols = this.SecondaryReferencedSymbols; - var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); + var symbolHash = ReferenceEqualityComparer.Instance.GetHashCode(symbol); // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find // the location we should start looking at. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 55dacb886dc62..cb1a742907d84 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -21,7 +21,6 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index cdf03429987b2..89309f9a843c1 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -16,7 +16,6 @@ using Microsoft.CodeAnalysis.Serialization; using Roslyn.Test.Utilities; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; #pragma warning disable CA1416 // Validate platform compatibility diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefLanguageServices.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefLanguageServices.cs index dc5e9b0e19416..de1198d6db9ba 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefLanguageServices.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefLanguageServices.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; [assembly: DebuggerTypeProxy(typeof(MefLanguageServices.LazyServiceMetadataDebuggerProxy), Target = typeof(ImmutableArray>))] diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs index 21e63c6d480be..e276d79f32e1d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; [assembly: DebuggerTypeProxy(typeof(MefWorkspaceServices.LazyServiceMetadataDebuggerProxy), Target = typeof(ImmutableArray>))] From afb35652e5bc56a454aff025c4dbb15e91ab939d Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Mon, 28 Apr 2025 12:12:31 -0700 Subject: [PATCH 085/114] Do not use nint / nuint in IL baselines (#78246) * Do not use nint / nuint in IL baselines This removes the use of `nint / nuint` in our default IL display baselines. Using them caused issues for our tests that run on both .NET Core and .NET Framework TFMs as it caused `IntPtr / UIntPtr` to display different in each case. This is an attempt to normalize our baselines so it's more resilient to tests like this. In general, our IL baselines should reflect metadata names, not C# type names, to further this goal. Did consider removing `UseSpecialNames` from `ILVisualizationFormat` but that was very disruptive. closes #78240 * NativeInteger tests * NumericIntPtrTests * silly * more * fix * more * PR feedback --- .../SymbolDisplayVisitor.Types.cs | 12 +- .../CodeGenOptimizedNullableOperators.cs | 16 +- .../CodeGenReadOnlySpanConstructionTest.cs | 22 +-- .../LocalStateTracingTests.cs | 12 +- .../Test/Emit2/Emit/NumericIntPtrTests.cs | 170 ++++++++-------- .../RuntimeProbing/ModuleCancellationTests.cs | 14 +- .../StackOverflowProbingTests.cs | 8 +- .../AttributeTests_NativeInteger.cs | 32 +-- .../Semantics/CollectionExpressionTests.cs | 20 +- .../Test/Emit3/Semantics/RecordTests.cs | 2 +- .../Semantic/Semantics/NativeIntegerTests.cs | 184 +++++++++--------- .../Symbols/InterfaceImplementationTests.cs | 16 +- .../Symbol/Symbols/Source/UsingAliasTests.cs | 2 +- .../SymbolDisplay/SymbolDisplayFormat.cs | 3 +- .../Test/Core/CompilationVerifier.cs | 29 +-- .../Test/Core/Metadata/ILBuilderVisualizer.cs | 25 +-- 16 files changed, 291 insertions(+), 276 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 02a0b61258d34..988793c924187 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -204,8 +204,16 @@ private void VisitNamedTypeWithoutNullability(INamedTypeSymbol symbol) return; } - if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.UseSpecialTypes) || - (symbol.IsNativeIntegerType && !Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType))) + if (symbol.IsNativeIntegerType) + { + if (!Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType) && + AddSpecialTypeKeyword(symbol)) + { + //if we're using special type keywords and this is a special type, then no other work is required + return; + } + } + else if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)) { if (AddSpecialTypeKeyword(symbol)) { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOptimizedNullableOperators.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOptimizedNullableOperators.cs index dff16da5b01e6..5de2c84e3264c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOptimizedNullableOperators.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOptimizedNullableOperators.cs @@ -4764,7 +4764,7 @@ public static void Run(nint? x) { // Code size 24 (0x18) .maxstack 2 - .locals init (nint? V_0, + .locals init (System.IntPtr? V_0, System.IntPtr V_1) IL_0000: ldarg.0 IL_0001: stloc.0 @@ -4772,11 +4772,11 @@ .locals init (nint? V_0, IL_0003: conv.i IL_0004: stloc.1 IL_0005: ldloca.s V_0 - IL_0007: call "nint nint?.GetValueOrDefault()" + IL_0007: call "System.IntPtr System.IntPtr?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 - IL_0011: call "bool nint?.HasValue.get" + IL_0011: call "bool System.IntPtr?.HasValue.get" IL_0016: and IL_0017: ret } @@ -4813,7 +4813,7 @@ public static void Run(nint? x) // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 - IL_0002: call "nint nint?.GetValueOrDefault()" + IL_0002: call "System.IntPtr System.IntPtr?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i IL_0009: ceq @@ -4851,7 +4851,7 @@ public static void Run(nuint? x) { // Code size 24 (0x18) .maxstack 2 - .locals init (nuint? V_0, + .locals init (System.UIntPtr? V_0, System.UIntPtr V_1) IL_0000: ldarg.0 IL_0001: stloc.0 @@ -4859,11 +4859,11 @@ .locals init (nuint? V_0, IL_0003: conv.i IL_0004: stloc.1 IL_0005: ldloca.s V_0 - IL_0007: call "nuint nuint?.GetValueOrDefault()" + IL_0007: call "System.UIntPtr System.UIntPtr?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 - IL_0011: call "bool nuint?.HasValue.get" + IL_0011: call "bool System.UIntPtr?.HasValue.get" IL_0016: and IL_0017: ret } @@ -4900,7 +4900,7 @@ public static void Run(nuint? x) // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 - IL_0002: call "nuint nuint?.GetValueOrDefault()" + IL_0002: call "System.UIntPtr System.UIntPtr?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i IL_0009: ceq diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs index 67b214853ab3a..3c16fff1632a6 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs @@ -1631,7 +1631,7 @@ .maxstack 5 .locals init (System.ReadOnlySpan V_0, //s1 System.ReadOnlySpan V_1, //s2 System.ReadOnlySpan V_2, //s3 - System.ReadOnlySpan V_3) //s4 + System.ReadOnlySpan V_3) //s4 IL_0000: ldloca.s V_0 IL_0002: ldsflda ".__StaticArrayInitTypeSize=3 .039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81" IL_0007: ldc.i4.3 @@ -1685,11 +1685,11 @@ .locals init (System.ReadOnlySpan V_0, //s1 IL_0081: stelem.i IL_0082: dup IL_0083: stsfld "nint[] .A52856308140261655B0EC09C0AC3BD6EA183729842D3B8029A1493EA881439B_B8" - IL_0088: call "System.ReadOnlySpan..ctor(nint[])" + IL_0088: call "System.ReadOnlySpan..ctor(System.IntPtr[])" IL_008d: ldloc.3 - IL_008e: call "void Test.Print(System.ReadOnlySpan)" + IL_008e: call "void Test.Print(System.ReadOnlySpan)" IL_0093: ldloca.s V_3 - IL_0095: call "bool System.ReadOnlySpan.IsEmpty.get" + IL_0095: call "bool System.ReadOnlySpan.IsEmpty.get" IL_009a: pop IL_009b: ret } @@ -3009,7 +3009,7 @@ .maxstack 4 IL_0013: stelem.i IL_0014: dup IL_0015: stsfld "nint[] .67ABDD721024F0FF4E0B3F4C2FC13BC5BAD42D0B7851D456D88D203D15AAA450_B8" - IL_001a: newobj "System.ReadOnlySpan..ctor(nint[])" + IL_001a: newobj "System.ReadOnlySpan..ctor(System.IntPtr[])" IL_001f: ret } """; @@ -3052,7 +3052,7 @@ .maxstack 4 IL_0017: stelem.i IL_0018: dup IL_0019: stsfld "nint[] .A2C70538651A7E9296B097E8C3DFC1B195A945802FFE45AA471868FBA6F1042E_B8" - IL_001e: newobj "System.ReadOnlySpan..ctor(nint[])" + IL_001e: newobj "System.ReadOnlySpan..ctor(System.IntPtr[])" IL_0023: ret } """; @@ -3102,7 +3102,7 @@ .maxstack 4 IL_0013: stelem.i IL_0014: dup IL_0015: stsfld "nuint[] .67ABDD721024F0FF4E0B3F4C2FC13BC5BAD42D0B7851D456D88D203D15AAA450_B16" - IL_001a: newobj "System.ReadOnlySpan..ctor(nuint[])" + IL_001a: newobj "System.ReadOnlySpan..ctor(System.UIntPtr[])" IL_001f: ret } """; @@ -3145,7 +3145,7 @@ .maxstack 4 IL_0013: stelem.i IL_0014: dup IL_0015: stsfld "nuint[] .AD95131BC0B799C0B1AF477FB14FCF26A6A9F76079E48BF090ACB7E8367BFD0E_B16" - IL_001a: newobj "System.ReadOnlySpan..ctor(nuint[])" + IL_001a: newobj "System.ReadOnlySpan..ctor(System.UIntPtr[])" IL_001f: ret } """; @@ -3192,7 +3192,7 @@ .maxstack 4 IL_0013: stelem.i IL_0014: dup IL_0015: stsfld "nuint[] .67ABDD721024F0FF4E0B3F4C2FC13BC5BAD42D0B7851D456D88D203D15AAA450_B16" - IL_001a: newobj "System.ReadOnlySpan..ctor(nuint[])" + IL_001a: newobj "System.ReadOnlySpan..ctor(System.UIntPtr[])" IL_001f: ret } """); @@ -3213,7 +3213,7 @@ .maxstack 4 IL_0013: stelem.i IL_0014: dup IL_0015: stsfld "nint[] .67ABDD721024F0FF4E0B3F4C2FC13BC5BAD42D0B7851D456D88D203D15AAA450_B8" - IL_001a: newobj "System.ReadOnlySpan..ctor(nint[])" + IL_001a: newobj "System.ReadOnlySpan..ctor(System.IntPtr[])" IL_001f: ret } """); @@ -3502,7 +3502,7 @@ .locals init (System.ReadOnlySpan<{{type}}> V_0, //s1 IL_003f: call "void System.Console.Write(bool)" IL_0044: ret } -"""); +""", ilFormat: SymbolDisplayFormat.ILVisualizationFormat.RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType)); } [Theory] diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs index 861f246abcc58..892b3022e0749 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs @@ -1415,7 +1415,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: }} IL_0055: ret }} -"); +", ilFormat: SymbolDisplayFormat.ILVisualizationFormat.RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType)); } [Theory] @@ -3145,7 +3145,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0) // sequence point: static Action A = new Action(() => { int x = 1; }); IL_000b: ldsfld ""C.<>c C.<>c.<>9"" IL_0010: ldftn ""void C.<>c.<.cctor>b__3_0()"" - IL_0016: newobj ""System.Action..ctor(object, nint)"" + IL_0016: newobj ""System.Action..ctor(object, System.IntPtr)"" IL_001b: stsfld ""System.Action C.A"" IL_0020: leave.s IL_002b } @@ -3736,7 +3736,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: F(() => c += 1); IL_0084: ldloc.s V_4 IL_0086: ldftn ""int C.<>c__DisplayClass0_2.
b__1()"" - IL_008c: newobj ""System.Func..ctor(object, nint)"" + IL_008c: newobj ""System.Func..ctor(object, System.IntPtr)"" IL_0091: call ""void C.F(System.Func)"" IL_0096: nop // sequence point: } @@ -3744,7 +3744,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: F(() => a += b); IL_0098: ldloc.3 IL_0099: ldftn ""int C.<>c__DisplayClass0_1.
b__0()"" - IL_009f: newobj ""System.Func..ctor(object, nint)"" + IL_009f: newobj ""System.Func..ctor(object, System.IntPtr)"" IL_00a4: call ""void C.F(System.Func)"" IL_00a9: nop // sequence point: } @@ -3956,7 +3956,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: F(b => ... }); IL_0027: ldloc.1 IL_0028: ldftn ""int C.<>c__DisplayClass1_0.b__0(int)"" - IL_002e: newobj ""System.Func..ctor(object, nint)"" + IL_002e: newobj ""System.Func..ctor(object, System.IntPtr)"" IL_0033: call ""int C.F(System.Func)"" IL_0038: pop // sequence point: } @@ -4020,7 +4020,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: return F(c => ++b); IL_004f: ldloc.1 IL_0050: ldftn ""int C.<>c__DisplayClass1_1.b__1(int)"" - IL_0056: newobj ""System.Func..ctor(object, nint)"" + IL_0056: newobj ""System.Func..ctor(object, System.IntPtr)"" IL_005b: call ""int C.F(System.Func)"" IL_0060: stloc.3 IL_0061: leave.s IL_006c diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/NumericIntPtrTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/NumericIntPtrTests.cs index 14e4d23beed4b..27ea729dd74c9 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/NumericIntPtrTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/NumericIntPtrTests.cs @@ -50,6 +50,12 @@ public class NumericIntPtrTests : CSharpTestBase internal static readonly ConversionKind[] ExplicitNullablePointerToInteger = new[] { ConversionKind.ExplicitNullable, ConversionKind.ExplicitPointerToInteger }; internal static readonly ConversionKind[] ExplicitNullableIdentity = new[] { ConversionKind.ExplicitNullable, ConversionKind.Identity }; + internal static readonly SymbolDisplayFormat TestFormat = SymbolDisplayFormat.TestFormat + .RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); + + internal static readonly SymbolDisplayFormat ILFormat = SymbolDisplayFormat.ILVisualizationFormat + .RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); + internal static bool IsNoConversion(ConversionKind[] conversionKinds) { return conversionKinds is [ConversionKind.NoConversion]; @@ -566,7 +572,7 @@ static void F2(nuint x, nuint y) static void verifyType(NamedTypeSymbol type, bool signed) { var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"System.Boolean {type}.Equals(System.Object obj)", @@ -696,7 +702,7 @@ static void M4(System.UIntPtr y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); - var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToTestDisplayString()); + var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToDisplayString(TestFormat)); var expectedLocals = new[] { "nint x1", @@ -1341,7 +1347,7 @@ .maxstack 1 IL_0021: sizeof ""nuint"" IL_0027: call ""void System.Console.Write(int)"" IL_002c: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -1465,7 +1471,7 @@ .maxstack 2 IL_0009: ldsfld ""nuint Program.F2"" IL_000e: add IL_000f: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -1499,7 +1505,7 @@ .maxstack 1 IL_0000: ldarga.s V_0 IL_0002: call ""string nint.ToString()"" IL_0007: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.F2", @"{ // Code size 7 (0x7) @@ -1507,7 +1513,7 @@ .maxstack 1 IL_0000: ldarg.0 IL_0001: box ""nint"" IL_0006: ret -}"); +}", ilFormat: ILFormat); } /// @@ -1673,7 +1679,7 @@ .locals init (nint? V_0, IL_0034: call ""readonly nuint nuint?.GetValueOrDefault()"" IL_0039: pop IL_003a: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("B.M2", @"{ // Code size 95 (0x5f) @@ -1717,7 +1723,7 @@ .locals init (int? V_0, IL_0054: newobj ""nuint?..ctor(nuint)"" IL_0059: stsfld ""nuint? A.F4"" IL_005e: ret -}"); +}", ilFormat: ILFormat); } [Theory, CombinatorialData] @@ -1842,7 +1848,7 @@ .locals init (nint? V_0, IL_00ec: newobj ""nuint?..ctor(nuint)"" IL_00f1: stsfld ""nuint? A.F4"" IL_00f6: ret -}"); +}", ilFormat: ILFormat); } [Fact, WorkItem(63860, "https://github.com/dotnet/roslyn/issues/63860")] @@ -1915,7 +1921,7 @@ static void F(nint{typeQualifier} x, nuint{typeQualifier} y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var nodes = tree.GetRoot().DescendantNodes().OfType(); - var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToTestDisplayString()).ToArray(); + var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToDisplayString(TestFormat)).ToArray(); var expectedOperators = new[] { "nint nint.op_UnaryPlus(nint value)", @@ -1967,7 +1973,7 @@ static void F({type}{typeQualifier} x, {type}{typeQualifier} y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var nodes = tree.GetRoot().DescendantNodes().OfType(); - var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToTestDisplayString()).ToArray(); + var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToDisplayString(TestFormat)).ToArray(); var nativeType = AsNative(type); var expectedOperators = new[] { @@ -2559,7 +2565,7 @@ .maxstack 1 IL_00cb: call ""void Program.F(nint)"" IL_00d0: ret }"; - verifier.VerifyIL("Program.Main", expectedIL); + verifier.VerifyIL("Program.Main", expectedIL, ilFormat: ILFormat); } [Theory] @@ -2672,7 +2678,7 @@ .maxstack 1 IL_0087: call ""void Program.F(nuint)"" IL_008c: ret }"; - verifier.VerifyIL("Program.Main", expectedIL); + verifier.VerifyIL("Program.Main", expectedIL, ilFormat: ILFormat); } [Fact] @@ -3234,7 +3240,7 @@ .locals init (long V_0) IL_00c7: ldarg.0 IL_00c8: ret } - "); + ", ilFormat: ILFormat); } [Fact] @@ -3396,7 +3402,7 @@ .locals init (ulong V_0) IL_00b6: ldarg.0 IL_00b7: ret } -"); +", ilFormat: ILFormat); } [Fact] @@ -6020,7 +6026,7 @@ enum E {{ }} if (expectedIL != null) { var verifier = CompileAndVerify(comp, verify: useUnsafeContext ? Verification.Skipped : Verification.FailsPEVerify); - verifier.VerifyIL("Program.Convert", expectedIL); + verifier.VerifyIL("Program.Convert", expectedIL, ilFormat: ILFormat); } static bool useUnsafe(string type) => type == "void*" || type == "delegate*"; @@ -6244,12 +6250,12 @@ static void Main() var model = comp.GetSemanticModel(tree); var expr = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput(expectedResult)); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -6620,12 +6626,12 @@ static void Main() isPrefix ? SyntaxKind.PreDecrementExpression : SyntaxKind.PostDecrementExpression; var expr = tree.GetRoot().DescendantNodes().Single(n => n.Kind() == kind); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput(expectedResult)); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -6852,12 +6858,12 @@ static void Main() var kind = (op == "++") ? SyntaxKind.PreIncrementExpression : SyntaxKind.PreDecrementExpression; var expr = tree.GetRoot().DescendantNodes().Single(n => n.Kind() == kind); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput(expectedResult)); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -8125,7 +8131,7 @@ void binaryOperator(string op, string leftType, string rightType, string expecte var model = comp.GetSemanticModel(tree); var expr = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { @@ -8206,7 +8212,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: add IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Subtract", @"{ // Code size 4 (0x4) @@ -8215,7 +8221,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: sub IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Multiply", @"{ // Code size 4 (0x4) @@ -8224,7 +8230,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: mul IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Divide", @"{ // Code size 4 (0x4) @@ -8233,7 +8239,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: div IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Mod", @"{ // Code size 4 (0x4) @@ -8242,7 +8248,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: rem IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Equals", @"{ // Code size 5 (0x5) @@ -8251,7 +8257,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: ceq IL_0004: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.NotEquals", @"{ // Code size 8 (0x8) @@ -8262,7 +8268,7 @@ .maxstack 2 IL_0004: ldc.i4.0 IL_0005: ceq IL_0007: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.LessThan", @"{ // Code size 5 (0x5) @@ -8271,7 +8277,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: clt IL_0004: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.LessThanOrEqual", @"{ // Code size 8 (0x8) @@ -8282,7 +8288,7 @@ .maxstack 2 IL_0004: ldc.i4.0 IL_0005: ceq IL_0007: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.GreaterThan", @"{ // Code size 5 (0x5) @@ -8291,7 +8297,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: cgt IL_0004: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.GreaterThanOrEqual", @"{ // Code size 8 (0x8) @@ -8302,7 +8308,7 @@ .maxstack 2 IL_0004: ldc.i4.0 IL_0005: ceq IL_0007: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.And", @"{ // Code size 4 (0x4) @@ -8311,7 +8317,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: and IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Or", @"{ // Code size 4 (0x4) @@ -8320,7 +8326,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: or IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.Xor", @"{ // Code size 4 (0x4) @@ -8329,7 +8335,7 @@ .maxstack 2 IL_0001: ldarg.1 IL_0002: xor IL_0003: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.ShiftLeft", @"{ // Code size 15 (0xf) @@ -8344,7 +8350,7 @@ .maxstack 4 IL_000c: and IL_000d: shl IL_000e: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.ShiftRight", @"{ // Code size 15 (0xf) @@ -8359,7 +8365,7 @@ .maxstack 4 IL_000c: and IL_000d: shr IL_000e: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -8570,7 +8576,7 @@ .maxstack 4 IL_000c: and IL_000d: shl IL_000e: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("Program.ShiftRight", @"{ // Code size 15 (0xf) @@ -8585,7 +8591,7 @@ .maxstack 4 IL_000c: and IL_000d: shr.un IL_000e: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -9329,16 +9335,16 @@ class C : I comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("I"); - Assert.Equal("S I.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("S I.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("S I.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("S I.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("S I.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("S I.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("S I.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("S I.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("C"); - Assert.Equal("S C.I.F1()", type.GetMember("I.F1").ToTestDisplayString()); - Assert.Equal("S C.I.F2()", type.GetMember("I.F2").ToTestDisplayString()); - Assert.Equal("S C.I.F3()", type.GetMember("I.F3").ToTestDisplayString()); - Assert.Equal("S C.I.F4()", type.GetMember("I.F4").ToTestDisplayString()); + Assert.Equal("S C.I.F1()", type.GetMember("I.F1").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F2()", type.GetMember("I.F2").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F3()", type.GetMember("I.F3").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F4()", type.GetMember("I.F4").ToDisplayString(TestFormat)); } [Fact] @@ -9363,16 +9369,16 @@ class B : A comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("A"); - Assert.Equal("nint[] A.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("nint[] A.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] A.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("nint[] A.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("nint[] A.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("nint[] A.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] A.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("nint[] A.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("B"); - Assert.Equal("nint[] B.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("nint[] B.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] B.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("nint[] B.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("nint[] B.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("nint[] B.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] B.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("nint[] B.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); } [Fact] @@ -10377,19 +10383,19 @@ void verify(CSharpCompilation comp) static void verifyIntPtr(TypeSymbol type) { Assert.Equal(SpecialType.System_IntPtr, type.SpecialType); - Assert.Equal("nint", type.ToTestDisplayString()); - Assert.Equal("nint", type.ToDisplayString(SymbolDisplayFormat.TestFormat)); - Assert.Equal("System.IntPtr", type.ToDisplayString(SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType))); - Assert.Equal("nint", type.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal("nint", type.ToDisplayString(TestFormat)); + Assert.Equal("nint", type.ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr", type.ToDisplayString(TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType))); + Assert.Equal("nint", type.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); } static void verifyUIntPtr(TypeSymbol type) { Assert.Equal(SpecialType.System_UIntPtr, type.SpecialType); - Assert.Equal("nuint", type.ToTestDisplayString()); - Assert.Equal("nuint", type.ToDisplayString(SymbolDisplayFormat.TestFormat)); - Assert.Equal("System.UIntPtr", type.ToDisplayString(SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType))); - Assert.Equal("nuint", type.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal("nuint", type.ToDisplayString(TestFormat)); + Assert.Equal("nuint", type.ToDisplayString(TestFormat)); + Assert.Equal("System.UIntPtr", type.ToDisplayString(TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType))); + Assert.Equal("nuint", type.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); } static void verifyCommon(TypeSymbol type) @@ -10545,8 +10551,8 @@ nuint M2(string s) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var returnStatements = tree.GetRoot().DescendantNodes().OfType().ToArray(); - Assert.Equal("nint nint.op_Implicit(System.String s)", model.GetConversion(returnStatements[0].Expression).Method.ToTestDisplayString()); - Assert.Equal("nuint nuint.op_Implicit(System.String s)", model.GetConversion(returnStatements[1].Expression).Method.ToTestDisplayString()); + Assert.Equal("nint nint.op_Implicit(System.String s)", model.GetConversion(returnStatements[0].Expression).Method.ToDisplayString(TestFormat)); + Assert.Equal("nuint nuint.op_Implicit(System.String s)", model.GetConversion(returnStatements[1].Expression).Method.ToDisplayString(TestFormat)); } [Fact] @@ -10728,7 +10734,7 @@ public class C static void verify(ModuleSymbol module) { var m = (MethodSymbol)module.GlobalNamespace.GetMember("C.M"); - Assert.Equal("nint C.M()", m.ToTestDisplayString()); + Assert.Equal("nint C.M()", m.ToDisplayString(TestFormat)); Assert.False(m.ReturnType.IsNativeIntegerWrapperType); } } @@ -10927,7 +10933,7 @@ public class Derived : Base var derivedM = (MethodSymbol)comp.GlobalNamespace.GetMember("Derived.M"); var derivedNint = (PENamedTypeSymbol)derivedM.ReturnType; - Assert.Equal("nint", derivedNint.ToTestDisplayString()); + Assert.Equal("nint", derivedNint.ToDisplayString(TestFormat)); Assert.Same(baseNint, derivedNint); } @@ -10962,8 +10968,8 @@ public class Derived : Base var derivedM = (MethodSymbol)comp.GlobalNamespace.GetMember("Derived.M"); var derivedNint = (NativeIntegerTypeSymbol)derivedM.ReturnType; - Assert.Equal("System.IntPtr", baseNint.ToTestDisplayString()); - Assert.Equal("nint", derivedNint.ToTestDisplayString()); + Assert.Equal("System.IntPtr", baseNint.ToDisplayString(TestFormat)); + Assert.Equal("nint", derivedNint.ToDisplayString(TestFormat)); Assert.Same(baseNint, derivedNint.UnderlyingNamedType); } @@ -10985,12 +10991,12 @@ public class C var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); comp.VerifyDiagnostics(); var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify); - verifier.VerifyIL("C.M1", shiftRight("nint")); - verifier.VerifyIL("C.M2", shiftRight("nuint")); - verifier.VerifyIL("C.M3", shiftRight("nint")); - verifier.VerifyIL("C.M4", shiftRight("nuint")); - verifier.VerifyIL("C.M5", shiftRight("nint")); - verifier.VerifyIL("C.M6", shiftRight("nuint")); + verifier.VerifyIL("C.M1", shiftRight("nint"), ilFormat: ILFormat); + verifier.VerifyIL("C.M2", shiftRight("nuint"), ilFormat: ILFormat); + verifier.VerifyIL("C.M3", shiftRight("nint"), ilFormat: ILFormat); + verifier.VerifyIL("C.M4", shiftRight("nuint"), ilFormat: ILFormat); + verifier.VerifyIL("C.M5", shiftRight("nint"), ilFormat: ILFormat); + verifier.VerifyIL("C.M6", shiftRight("nuint"), ilFormat: ILFormat); return; @@ -11296,7 +11302,7 @@ public class C var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var symbol = (IFieldSymbol)model.GetSymbolInfo(cref).Symbol; - Assert.Equal("nint nint.Zero", symbol.ToTestDisplayString()); + Assert.Equal("nint nint.Zero", symbol.ToDisplayString(TestFormat)); } [Fact, WorkItem(43347, "https://github.com/dotnet/roslyn/issues/43347")] @@ -11424,7 +11430,7 @@ .locals init (nint V_0, IL_002b: newobj ""nint?..ctor(nint)"" IL_0030: ret } -"); +", ilFormat: ILFormat); // lifted value and lifted count compileAndVerify(""" @@ -11468,7 +11474,7 @@ .locals init (nint? V_0, IL_0039: newobj ""nint?..ctor(nint)"" IL_003e: ret } -"); +", ilFormat: ILFormat); return; static string nint_shr(int count) => shift(count, "nint", "shr"); @@ -11686,7 +11692,7 @@ public static unsafe void Main() """; var comp = CreateCompilation(source, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net70); var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput("RAN")); - verifier.VerifyIL("C.M", expectedIL); + verifier.VerifyIL("C.M", expectedIL, ilFormat: ILFormat); } } @@ -11719,7 +11725,7 @@ .maxstack 4 IL_000e: shr IL_000f: ret } -"); +", ilFormat: ILFormat); } [WorkItem(63348, "https://github.com/dotnet/roslyn/issues/63348")] diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/ModuleCancellationTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/ModuleCancellationTests.cs index 6771a4eb4c05c..cb82f66d27300 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/ModuleCancellationTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/ModuleCancellationTests.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests; public sealed class ModuleCancellationTests : CSharpTestBase { -#if NET - const string NativeIntDisplay = "nint"; -#else - const string NativeIntDisplay = "System.IntPtr"; -#endif - private static readonly EmitOptions s_emitOptions = EmitOptions.Default.WithInstrumentationKinds([InstrumentationKind.ModuleCancellation]); private CompilationVerifier CompileAndVerify(string source, string? expectedOutput = null, CSharpCompilationOptions? options = null, Verification? verification = null) @@ -86,7 +80,7 @@ .maxstack 3 IL_0015: pop IL_0016: ldsfld "C.<>c C.<>c.<>9" IL_001b: ldftn "int C.<>c.b__0_1()" - IL_0021: newobj "System.Func..ctor(object, {{NativeIntDisplay}})" + IL_0021: newobj "System.Func..ctor(object, System.IntPtr)" IL_0026: dup IL_0027: stsfld "System.Func C.<>c.<>9__0_1" IL_002c: call "void C.F(System.Func)" @@ -1483,7 +1477,7 @@ .locals init (System.Action V_0) //g // sequence point: var g = new Action(G); IL_000b: ldarg.0 IL_000c: ldftn "void C.G(System.Threading.CancellationToken)" - IL_0012: newobj "System.Action..ctor(object, {{NativeIntDisplay}})" + IL_0012: newobj "System.Action..ctor(object, System.IntPtr)" IL_0017: stloc.0 // sequence point: g(token); IL_0018: ldloc.0 @@ -1566,7 +1560,7 @@ .locals init (D V_0) //g // sequence point: var g = new D(G); IL_000b: ldarg.0 IL_000c: ldftn "void C.G(int, System.Threading.CancellationToken)" - IL_0012: newobj "D..ctor(object, {{NativeIntDisplay}})" + IL_0012: newobj "D..ctor(object, System.IntPtr)" IL_0017: stloc.0 // sequence point: g(token: token, a: 1); IL_0018: ldloc.0 @@ -1618,7 +1612,7 @@ .locals init (D V_0) //g // sequence point: var g = new D(G); IL_000b: ldarg.0 IL_000c: ldftn "void C.G(int, System.Threading.CancellationToken)" - IL_0012: newobj "D..ctor(object, {{NativeIntDisplay}})" + IL_0012: newobj "D..ctor(object, System.IntPtr)" IL_0017: stloc.0 // sequence point: g(1); IL_0018: ldloc.0 diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/StackOverflowProbingTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/StackOverflowProbingTests.cs index be082643a0e46..71affb7129233 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/StackOverflowProbingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/RuntimeProbing/StackOverflowProbingTests.cs @@ -13,12 +13,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests; public sealed class StackOverflowProbingTests : CSharpTestBase { -#if NET - const string NativeIntDisplay = "nint"; -#else - const string NativeIntDisplay = "System.IntPtr"; -#endif - private static readonly EmitOptions s_emitOptions = EmitOptions.Default.WithInstrumentationKinds([InstrumentationKind.StackOverflowProbing]); private CompilationVerifier CompileAndVerify(string source, string? expectedOutput = null, CSharpCompilationOptions? options = null, Verification? verification = null) @@ -79,7 +73,7 @@ .maxstack 3 IL_0011: pop IL_0012: ldsfld "C.<>c C.<>c.<>9" IL_0017: ldftn "int C.<>c.b__0_1()" - IL_001d: newobj "System.Func..ctor(object, {{NativeIntDisplay}})" + IL_001d: newobj "System.Func..ctor(object, System.IntPtr)" IL_0022: dup IL_0023: stsfld "System.Func C.<>c.<>9__0_1" IL_0028: call "void C.F(System.Func)" diff --git a/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_NativeInteger.cs b/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_NativeInteger.cs index 89a7754b0ba16..f0e260ac5b56d 100644 --- a/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_NativeInteger.cs +++ b/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_NativeInteger.cs @@ -20,7 +20,9 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class AttributeTests_NativeInteger : CSharpTestBase { - private static readonly SymbolDisplayFormat FormatWithSpecialTypes = SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + private static readonly SymbolDisplayFormat FormatWithSpecialTypes = SymbolDisplayFormat.TestFormat + .WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes) + .RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); [Fact] public void EmptyProject() @@ -1526,11 +1528,11 @@ static void symbolValidator(ModuleSymbol module) AssertNativeIntegerAttributes(module, expectedAttributes); var c = module.GlobalNamespace.GetTypeMember("C"); - assert("C", "F0"); - assert("C", "F1"); - assert("C", "F2"); - assert("C", "F3"); - assert("C", "F4"); + assert("C", "F0"); + assert("C", "F1"); + assert("C", "F2"); + assert("C", "F3"); + assert("C", "F4"); void assert(string expectedType, string fieldName) { @@ -1575,15 +1577,15 @@ static void symbolValidator(ModuleSymbol module) AssertNativeIntegerAttributes(module, expectedAttributes); var c = module.GlobalNamespace.GetTypeMember("C"); - assert("delegate*", "F0"); - assert("delegate*", "F1"); - assert("delegate*", "F2"); - assert("delegate*", "F3"); - assert("delegate*", "F4"); - assert("delegate*, nint>", "F5"); - assert("delegate*>", "F6"); - assert("delegate*, System.IntPtr>", "F7"); - assert("delegate*>", "F8"); + assert("delegate*", "F0"); + assert("delegate*", "F1"); + assert("delegate*", "F2"); + assert("delegate*", "F3"); + assert("delegate*", "F4"); + assert("delegate*, System.IntPtr>", "F5"); + assert("delegate*>", "F6"); + assert("delegate*, System.IntPtr>", "F7"); + assert("delegate*>", "F8"); void assert(string expectedType, string fieldName) { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 78cc248bb4826..45dd91e11bc0f 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -24811,7 +24811,7 @@ .locals init (System.ReadOnlySpan V_0, //r1 IL_0008: pop IL_0009: ldsfld "C.<>c C.<>c.<>9" IL_000e: ldftn "void C.<>c.<.ctor>b__1_1(T)" - IL_0014: newobj "System.Action..ctor(object, nint)" + IL_0014: newobj "System.Action..ctor(object, System.IntPtr)" IL_0019: dup IL_001a: stsfld "System.Action C.<>c.<>9__1_1" IL_001f: ldarg.2 @@ -25291,8 +25291,8 @@ static void Report(ReadOnlySpan s) .maxstack 1 .locals init (object V_0, string V_1, - nint V_2, - nuint V_3) + System.IntPtr V_2, + System.UIntPtr V_3) IL_0000: ldstr "1" IL_0005: stloc.0 IL_0006: ldloca.s V_0 @@ -25307,14 +25307,14 @@ .locals init (object V_0, IL_0025: conv.i IL_0026: stloc.2 IL_0027: ldloca.s V_2 - IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly nint)" - IL_002e: call "void Program.Report(System.ReadOnlySpan)" + IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly System.IntPtr)" + IL_002e: call "void Program.Report(System.ReadOnlySpan)" IL_0033: ldc.i4.4 IL_0034: conv.i IL_0035: stloc.3 IL_0036: ldloca.s V_3 - IL_0038: newobj "System.ReadOnlySpan..ctor(ref readonly nuint)" - IL_003d: call "void Program.Report(System.ReadOnlySpan)" + IL_0038: newobj "System.ReadOnlySpan..ctor(ref readonly System.UIntPtr)" + IL_003d: call "void Program.Report(System.ReadOnlySpan)" IL_0042: ret } """); @@ -35479,7 +35479,7 @@ nint[] M3(List list) // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.1 - IL_0001: callvirt "nint[] System.Collections.Generic.List.ToArray()" + IL_0001: callvirt "System.IntPtr[] System.Collections.Generic.List.ToArray()" IL_0006: ret } """); @@ -35489,7 +35489,7 @@ .maxstack 1 // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.1 - IL_0001: call "System.Collections.Generic.List System.Linq.Enumerable.ToList(System.Collections.Generic.IEnumerable)" + IL_0001: call "System.Collections.Generic.List System.Linq.Enumerable.ToList(System.Collections.Generic.IEnumerable)" IL_0006: ret } """); @@ -35499,7 +35499,7 @@ .maxstack 1 // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.1 - IL_0001: callvirt "nint[] System.Collections.Generic.List.ToArray()" + IL_0001: callvirt "System.IntPtr[] System.Collections.Generic.List.ToArray()" IL_0006: ret } """); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs index 0b8fa5baa41b0..cc001d58ca554 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/RecordTests.cs @@ -10605,7 +10605,7 @@ record C(dynamic P1, object[] P2, object P3, object?[] P4, (int, int) P5, (int X "System.Object[] C.P4 { get; }", "(System.Int32 X, System.Int32 Y) C.P5 { get; }", "(System.Int32, System.Int32)[] C.P6 { get; }", - "nint C.P7 { get; }", + "System.IntPtr C.P7 { get; }", "System.UIntPtr[] C.P8 { get; }" }; AssertEx.Equal(expectedMembers, actualMembers); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs index ab2498b030803..0263c33be92e3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NativeIntegerTests.cs @@ -42,6 +42,12 @@ public class NativeIntegerTests : CSharpTestBase internal static readonly ConversionKind[] ExplicitNullablePointerToInteger = new[] { ConversionKind.ExplicitNullable, ConversionKind.ExplicitPointerToInteger }; internal static readonly ConversionKind[] ExplicitNullableIdentity = new[] { ConversionKind.ExplicitNullable, ConversionKind.Identity }; + internal static readonly SymbolDisplayFormat TestFormat = SymbolDisplayFormat.TestFormat + .RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); + + internal static readonly SymbolDisplayFormat ILFormat = SymbolDisplayFormat.ILVisualizationFormat + .RemoveCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); + internal static bool IsNoConversion(ConversionKind[] conversionKinds) { return conversionKinds is [ConversionKind.NoConversion]; @@ -111,12 +117,12 @@ public void TypeDefinitions_FromMetadata() VerifyType(type.GetPublicSymbol(), signed: false, isNativeInt: false); var method = comp.GetMember("I.F1"); - Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToDisplayString(TestFormat)); Assert.Equal("Sub I.F1(x As System.IntPtr, y As System.IntPtr)", VisualBasic.SymbolDisplay.ToDisplayString(method.GetPublicSymbol(), SymbolDisplayFormat.TestFormat)); VerifyTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: true); method = comp.GetMember("I.F2"); - Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToDisplayString(TestFormat)); Assert.Equal("Sub I.F2(x As System.UIntPtr, y As System.UIntPtr)", VisualBasic.SymbolDisplay.ToDisplayString(method.GetPublicSymbol(), SymbolDisplayFormat.TestFormat)); VerifyTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: false); } @@ -207,11 +213,11 @@ static void verify(CSharpCompilation comp) VerifyType(type.GetPublicSymbol(), signed: false, isNativeInt: false); var method = comp.GetMember("I.F1"); - Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToDisplayString(TestFormat)); VerifyTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: true); method = comp.GetMember("I.F2"); - Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToDisplayString(TestFormat)); VerifyTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: false); } } @@ -711,11 +717,11 @@ public struct Void { } static void verify(CSharpCompilation comp) { var method = comp.GetMember("I.F1"); - Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F1(System.IntPtr x, nint y)", method.ToDisplayString(TestFormat)); VerifyErrorTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: true); method = comp.GetMember("I.F2"); - Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("void I.F2(System.UIntPtr x, nuint y)", method.ToDisplayString(TestFormat)); VerifyErrorTypes((NamedTypeSymbol)method.Parameters[0].Type, (NamedTypeSymbol)method.Parameters[1].Type, signed: false); } } @@ -808,7 +814,7 @@ static void Main() static void verifyField(FieldSymbol field, string expectedSymbol, AssemblySymbol expectedAssembly) { Assert.IsType(field); - Assert.Equal(expectedSymbol, field.ToTestDisplayString()); + Assert.Equal(expectedSymbol, field.ToDisplayString(TestFormat)); var type = (NamedTypeSymbol)field.Type; Assert.True(type.IsNativeIntegerWrapperType); Assert.IsType(type); @@ -921,7 +927,7 @@ static void Main() static void verifyField(FieldSymbol field, string expectedSymbol, AssemblySymbol expectedAssembly) { Assert.IsType(field); - Assert.Equal(expectedSymbol, field.ToTestDisplayString()); + Assert.Equal(expectedSymbol, field.ToDisplayString(TestFormat)); var type = (NamedTypeSymbol)field.Type; Assert.True(type.IsNativeIntegerWrapperType); Assert.IsType(type); @@ -1028,7 +1034,7 @@ static void Main() static void verifyField(FieldSymbol field, string expectedSymbol, AssemblySymbol expectedAssembly) { Assert.IsType(field); - Assert.Equal(expectedSymbol, field.ToTestDisplayString()); + Assert.Equal(expectedSymbol, field.ToDisplayString(TestFormat)); var type = (NamedTypeSymbol)field.Type; Assert.True(type.IsNativeIntegerWrapperType); Assert.IsType(type); @@ -1100,7 +1106,7 @@ static void Main() static void verifyField(FieldSymbol field, string expectedSymbol) { Assert.IsType(field); - Assert.Equal(expectedSymbol, field.ToTestDisplayString()); + Assert.Equal(expectedSymbol, field.ToDisplayString(TestFormat)); var type = (NamedTypeSymbol)field.Type; Assert.True(type.IsNativeIntegerWrapperType); Assert.IsType(type); @@ -1785,7 +1791,7 @@ static nuint F2() var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); - var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToTestDisplayString()); + var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToDisplayString(TestFormat)); var expectedLocals = new[] { "nint x1", @@ -1805,7 +1811,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) VerifyType(type.GetPublicSymbol(), signed: signed, isNativeInt: true); var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"System.Boolean {type}.TryParse(System.String s, out {type} value)", @@ -1970,7 +1976,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) VerifyType(type.GetPublicSymbol(), signed: signed, isNativeInt: true); var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"System.Boolean {type}.Equals({type} other)", @@ -2140,7 +2146,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) VerifyType(type.GetPublicSymbol(), signed: signed, isNativeInt: true); var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"{type}..ctor()", @@ -2236,7 +2242,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) VerifyType(type.GetPublicSymbol(), signed: signed, isNativeInt: true); var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"System.Boolean {type}.Equals(System.Object obj)", @@ -2342,7 +2348,7 @@ static void M2(nuint y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); - var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToTestDisplayString()); + var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToDisplayString(TestFormat)); var expectedLocals = new[] { "nint x1", @@ -2364,7 +2370,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) var underlyingType = type.NativeIntegerUnderlyingType; var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"{type}..ctor()", @@ -2462,7 +2468,7 @@ static void M2(nuint y) var tree = compB.SyntaxTrees[0]; var model = compB.GetSemanticModel(tree); - var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToTestDisplayString()); + var actualLocals = tree.GetRoot().DescendantNodes().OfType().Select(d => model.GetDeclaredSymbol(d).ToDisplayString(TestFormat)); var expectedLocals = new[] { "nint x1", @@ -2489,7 +2495,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) Assert.True(member.GetExplicitInterfaceImplementations().IsEmpty); } - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"{type} {type}.F()", @@ -2640,7 +2646,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) var underlyingType = type.NativeIntegerUnderlyingType; var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"{type} {type}.F3()", @@ -2742,7 +2748,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) var underlyingType = type.NativeIntegerUnderlyingType; var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"{type}..ctor()", @@ -2882,7 +2888,7 @@ static void verifyType(NamedTypeSymbol type, bool signed) var underlyingType = type.NativeIntegerUnderlyingType; var members = type.GetMembers().Sort(SymbolComparison); - var actualMembers = members.SelectAsArray(m => m.ToTestDisplayString()); + var actualMembers = members.SelectAsArray(m => m.ToDisplayString(TestFormat)); var expectedMembers = new[] { $"System.IComparable<{type} modopt({underlyingType})> {type}.F5()", @@ -2990,7 +2996,7 @@ .maxstack 0 IL_0000: call ""void Program.F()"" IL_0005: call ""void Program.F()"" IL_000a: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -3073,7 +3079,7 @@ .maxstack 4 IL_0044: stelem.i IL_0045: call ""void Program.Report(nuint[])"" IL_004a: ret -}"); +}", ilFormat: ILFormat); } [Fact] @@ -3633,10 +3639,10 @@ static void verify(CSharpCompilation comp) var nodes = tree.GetRoot().DescendantNodes().ToArray(); var model = comp.GetSemanticModel(tree); var underlyingType = model.GetDeclaredSymbol(nodes.OfType().Single()); - Assert.Equal("nint", underlyingType.ToTestDisplayString()); + Assert.Equal("nint", underlyingType.ToDisplayString()); Assert.Equal(SpecialType.None, underlyingType.SpecialType); var method = model.GetDeclaredSymbol(nodes.OfType().Single()); - Assert.Equal("nint I.Add(nint x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("nint I.Add(nint x, nuint y)", method.ToDisplayString(TestFormat)); var underlyingType0 = method.Parameters[0].Type.GetSymbol(); var underlyingType1 = method.Parameters[1].Type.GetSymbol(); Assert.Equal(SpecialType.None, underlyingType0.SpecialType); @@ -3678,7 +3684,7 @@ void verify(CSharpCompilation comp) @"System.Int16 System.Object"); var method = comp.GetMember("Program.F"); - Assert.Equal("System.Int16 Program.F(System.Int16 x, System.Object y)", method.ToTestDisplayString()); + Assert.Equal("System.Int16 Program.F(System.Int16 x, System.Object y)", method.ToDisplayString(TestFormat)); var underlyingType0 = (NamedTypeSymbol)method.Parameters[0].Type; var underlyingType1 = (NamedTypeSymbol)method.Parameters[1].Type; Assert.Equal(SpecialType.System_Int16, underlyingType0.SpecialType); @@ -3712,7 +3718,7 @@ class Program static void verify(CSharpCompilation comp) { var method = comp.GetMember("Program.F"); - Assert.Equal("System.Int16 Program.F(System.Int16 x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("System.Int16 Program.F(System.Int16 x, nuint y)", method.ToDisplayString(TestFormat)); var underlyingType0 = (NamedTypeSymbol)method.Parameters[0].Type; var underlyingType1 = (NamedTypeSymbol)method.Parameters[1].Type; Assert.Equal(SpecialType.System_Int16, underlyingType0.SpecialType); @@ -3746,7 +3752,7 @@ class Program static void verify(CSharpCompilation comp) { var method = comp.GetMember("Program.F"); - Assert.Equal("System.Int16 Program.F(System.Int16 x, nuint y)", method.ToTestDisplayString()); + Assert.Equal("System.Int16 Program.F(System.Int16 x, nuint y)", method.ToDisplayString(TestFormat)); var underlyingType0 = (NamedTypeSymbol)method.Parameters[0].Type; var underlyingType1 = (NamedTypeSymbol)method.Parameters[1].Type; Assert.Equal(SpecialType.System_Int16, underlyingType0.SpecialType); @@ -4515,7 +4521,7 @@ .maxstack 2 IL_0009: ldsfld ""nuint Program.F2"" IL_000e: add IL_000f: ret -}"); +}", ilFormat: ILFormat); } // PEVerify should succeed. Previously, PEVerify reported duplicate @@ -4704,7 +4710,7 @@ .locals init (System.IntPtr V_0) IL_0007: ldloca.s V_0 IL_0009: call ""string System.IntPtr.ToString()"" IL_000e: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("MyInt.GetHashCode", @"{ // Code size 29 (0x1d) @@ -4717,7 +4723,7 @@ .maxstack 2 IL_0012: newobj ""System.Func..ctor(object, System.IntPtr)"" IL_0017: callvirt ""int System.Func.Invoke()"" IL_001c: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("MyInt.Equals", @"{ // Code size 51 (0x33) @@ -4742,7 +4748,7 @@ .locals init (System.IntPtr V_0, IL_0028: box ""nint?"" IL_002d: call ""bool System.IntPtr.Equals(object)"" IL_0032: ret -}"); +}", ilFormat: ILFormat); } /// @@ -4916,7 +4922,7 @@ .locals init (nint? V_0, IL_0034: call ""nuint nuint?.GetValueOrDefault()"" IL_0039: pop IL_003a: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("B.M2", @"{ // Code size 95 (0x5f) @@ -4960,7 +4966,7 @@ .locals init (int? V_0, IL_0054: newobj ""nuint?..ctor(nuint)"" IL_0059: stsfld ""nuint? A.F4"" IL_005e: ret -}"); +}", ilFormat: ILFormat); } [WorkItem(3259, "https://github.com/dotnet/csharplang/issues/3259")] @@ -5057,7 +5063,7 @@ .locals init (nint? V_0, IL_008c: div.un IL_008d: pop IL_008e: ret -}"); +}", ilFormat: ILFormat); comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.Regular8); comp.VerifyEmitDiagnostics( @@ -5273,7 +5279,7 @@ .locals init (nint? V_0, IL_0034: call ""nuint nuint?.GetValueOrDefault()"" IL_0039: pop IL_003a: ret -}"); +}", ilFormat: ILFormat); verifier.VerifyIL("B.M2", @"{ // Code size 95 (0x5f) @@ -5317,7 +5323,7 @@ .locals init (int? V_0, IL_0054: newobj ""nuint?..ctor(nuint)"" IL_0059: stsfld ""nuint? A.F4"" IL_005e: ret -}"); +}", ilFormat: ILFormat); } [WorkItem(3259, "https://github.com/dotnet/csharplang/issues/3259")] @@ -5441,7 +5447,7 @@ .locals init (nint? V_0, IL_00ec: newobj ""nuint?..ctor(nuint)"" IL_00f1: stsfld ""nuint? A.F4"" IL_00f6: ret -}"); +}", ilFormat: ILFormat); comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( @@ -6058,7 +6064,7 @@ static void F(nint{typeQualifier} x, nuint{typeQualifier} y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var nodes = tree.GetRoot().DescendantNodes().OfType(); - var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToTestDisplayString()).ToArray(); + var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToDisplayString(TestFormat)).ToArray(); var expectedOperators = new[] { "nint nint.op_UnaryPlus(nint value)", @@ -6106,7 +6112,7 @@ static void F({type}{typeQualifier} x, {type}{typeQualifier} y) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var nodes = tree.GetRoot().DescendantNodes().OfType(); - var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToTestDisplayString()).ToArray(); + var actualOperators = nodes.Select(n => model.GetSymbolInfo(n).Symbol.ToDisplayString(TestFormat)).ToArray(); var expectedOperators = new[] { $"{type} {type}.op_Addition({type} left, {type} right)", @@ -6693,7 +6699,7 @@ .maxstack 1 IL_00cb: call ""void Program.F(nint)"" IL_00d0: ret }"; - verifier.VerifyIL("Program.Main", expectedIL); + verifier.VerifyIL("Program.Main", expectedIL, ilFormat: ILFormat); } [Fact] @@ -6804,7 +6810,7 @@ .maxstack 1 IL_0087: call ""void Program.F(nuint)"" IL_008c: ret }"; - verifier.VerifyIL("Program.Main", expectedIL); + verifier.VerifyIL("Program.Main", expectedIL, ilFormat: ILFormat); } [Fact] @@ -7394,7 +7400,7 @@ .locals init (long V_0) IL_00c7: ldarg.0 IL_00c8: ret } -"); +", ilFormat: ILFormat); } [Fact] @@ -7555,7 +7561,7 @@ .locals init (ulong V_0) IL_00b6: ldarg.0 IL_00b7: ret } -"); +", ilFormat: ILFormat); } [Fact] @@ -9509,7 +9515,7 @@ enum E {{ }} if (expectedIL != null) { var verifier = CompileAndVerify(comp, verify: useUnsafeContext || !verify ? Verification.Skipped : Verification.Passes); - verifier.VerifyIL("Program.Convert", expectedIL); + verifier.VerifyIL("Program.Convert", expectedIL, ilFormat: ILFormat); } static bool useUnsafe(string type) => type == "void*" || type == "delegate*"; @@ -9728,12 +9734,12 @@ static void Main() var model = comp.GetSemanticModel(tree); var expr = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, expectedOutput: expectedResult); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -10105,12 +10111,12 @@ static void Main() isPrefix ? SyntaxKind.PreDecrementExpression : SyntaxKind.PostDecrementExpression; var expr = tree.GetRoot().DescendantNodes().Single(n => n.Kind() == kind); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, expectedOutput: expectedResult); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -10330,12 +10336,12 @@ static void Main() var kind = (op == "++") ? SyntaxKind.PreIncrementExpression : SyntaxKind.PreDecrementExpression; var expr = tree.GetRoot().DescendantNodes().Single(n => n.Kind() == kind); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { var verifier = CompileAndVerify(comp, expectedOutput: expectedResult); - verifier.VerifyIL("Program.Evaluate", expectedIL); + verifier.VerifyIL("Program.Evaluate", expectedIL, ilFormat: ILFormat); } } } @@ -12379,7 +12385,7 @@ void binaryOperator(string op, string leftType, string rightType, string expecte var model = comp.GetSemanticModel(tree); var expr = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(expr); - Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(SymbolDisplayFormat.TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); + Assert.Equal(expectedSymbol, symbolInfo.Symbol?.ToDisplayString(TestFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes))); if (expectedDiagnostics.Length == 0) { @@ -13569,16 +13575,16 @@ class C : I comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("I"); - Assert.Equal("S I.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("S I.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("S I.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("S I.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("S I.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("S I.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("S I.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("S I.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("C"); - Assert.Equal("S C.I.F1()", type.GetMember("I.F1").ToTestDisplayString()); - Assert.Equal("S C.I.F2()", type.GetMember("I.F2").ToTestDisplayString()); - Assert.Equal("S C.I.F3()", type.GetMember("I.F3").ToTestDisplayString()); - Assert.Equal("S C.I.F4()", type.GetMember("I.F4").ToTestDisplayString()); + Assert.Equal("S C.I.F1()", type.GetMember("I.F1").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F2()", type.GetMember("I.F2").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F3()", type.GetMember("I.F3").ToDisplayString(TestFormat)); + Assert.Equal("S C.I.F4()", type.GetMember("I.F4").ToDisplayString(TestFormat)); } [WorkItem(42500, "https://github.com/dotnet/roslyn/issues/42500")] @@ -13605,16 +13611,16 @@ class B : A comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("A"); - Assert.Equal("nint[] A.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] A.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] A.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] A.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("nint[] A.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] A.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] A.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] A.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("B"); - Assert.Equal("System.IntPtr[] B.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("nint[] B.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] B.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] B.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("System.IntPtr[] B.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("nint[] B.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] B.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] B.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); } [WorkItem(42500, "https://github.com/dotnet/roslyn/issues/42500")] @@ -13664,16 +13670,16 @@ public override void F4(System.IntPtr i) { } comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("A"); - Assert.Equal("void A.F1(nint modopt(System.Int32) i)", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("void A.F2(System.IntPtr modopt(System.Int32) i)", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("void A.F3(nint modopt(System.Int32) i)", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("void A.F4(System.IntPtr modopt(System.Int32) i)", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("void A.F1(nint modopt(System.Int32) i)", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("void A.F2(System.IntPtr modopt(System.Int32) i)", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("void A.F3(nint modopt(System.Int32) i)", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("void A.F4(System.IntPtr modopt(System.Int32) i)", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("B"); - Assert.Equal("void B.F1(System.IntPtr modopt(System.Int32) i)", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("void B.F2(nint modopt(System.Int32) i)", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("void B.F3(nint modopt(System.Int32) i)", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("void B.F4(System.IntPtr modopt(System.Int32) i)", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("void B.F1(System.IntPtr modopt(System.Int32) i)", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("void B.F2(nint modopt(System.Int32) i)", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("void B.F3(nint modopt(System.Int32) i)", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("void B.F4(System.IntPtr modopt(System.Int32) i)", type.GetMember("F4").ToDisplayString(TestFormat)); } [WorkItem(42500, "https://github.com/dotnet/roslyn/issues/42500")] @@ -13727,16 +13733,16 @@ .method public virtual native int[] modopt(int32) F4() comp.VerifyEmitDiagnostics(); var type = comp.GetTypeByMetadataName("A"); - Assert.Equal("nint[] modopt(System.Int32) A.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] modopt(System.Int32) A.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] modopt(System.Int32) A.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] modopt(System.Int32) A.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("nint[] modopt(System.Int32) A.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] modopt(System.Int32) A.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] modopt(System.Int32) A.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] modopt(System.Int32) A.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); type = comp.GetTypeByMetadataName("B"); - Assert.Equal("System.IntPtr[] modopt(System.Int32) B.F1()", type.GetMember("F1").ToTestDisplayString()); - Assert.Equal("nint[] modopt(System.Int32) B.F2()", type.GetMember("F2").ToTestDisplayString()); - Assert.Equal("nint[] modopt(System.Int32) B.F3()", type.GetMember("F3").ToTestDisplayString()); - Assert.Equal("System.IntPtr[] modopt(System.Int32) B.F4()", type.GetMember("F4").ToTestDisplayString()); + Assert.Equal("System.IntPtr[] modopt(System.Int32) B.F1()", type.GetMember("F1").ToDisplayString(TestFormat)); + Assert.Equal("nint[] modopt(System.Int32) B.F2()", type.GetMember("F2").ToDisplayString(TestFormat)); + Assert.Equal("nint[] modopt(System.Int32) B.F3()", type.GetMember("F3").ToDisplayString(TestFormat)); + Assert.Equal("System.IntPtr[] modopt(System.Int32) B.F4()", type.GetMember("F4").ToDisplayString(TestFormat)); } [WorkItem(42457, "https://github.com/dotnet/roslyn/issues/42457")] @@ -15573,7 +15579,7 @@ .locals init (System.IntPtr V_0, IL_002b: newobj ""nint?..ctor(nint)"" IL_0030: ret } -"); +", ilFormat: ILFormat); // lifted value and lifted count CompileAndVerify(""" @@ -15617,7 +15623,7 @@ .locals init (nint? V_0, IL_0039: newobj ""nint?..ctor(nint)"" IL_003e: ret } -"); +", ilFormat: ILFormat); return; static string nint_shr(int count) => shift(count, "System.IntPtr", "shr"); @@ -15830,7 +15836,7 @@ public static unsafe void Main() """; var comp = CreateCompilation(source, options: TestOptions.UnsafeReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "RAN"); - verifier.VerifyIL("C.M", expectedIL); + verifier.VerifyIL("C.M", expectedIL, ilFormat: ILFormat); } } @@ -15883,7 +15889,7 @@ .maxstack 1 IL_0000: ldarg.0 IL_0001: ret } -"); +", ilFormat: ILFormat); } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/InterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/InterfaceImplementationTests.cs index 0897a9360a77e..ff740d0d07834 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/InterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/InterfaceImplementationTests.cs @@ -2553,12 +2553,12 @@ static void Main() [InlineData("object", "dynamic", "dynamic", "System.Object", false)] [InlineData("(int X, int Y)", "(int X, int Y)", "(System.Int32 X, System.Int32 Y)", "System.ValueTuple`2[System.Int32,System.Int32]", true)] [InlineData("(int X, int Y)", "(int X, int Y)", "(System.Int32 X, System.Int32 Y)", "System.ValueTuple`2[System.Int32,System.Int32]", false)] - [InlineData("nint", "nint", "nint", "System.IntPtr", true)] - [InlineData("nint", "nint", "nint", "System.IntPtr", false)] + [InlineData("nint", "nint", "System.IntPtr", "System.IntPtr", true)] + [InlineData("nint", "nint", "System.IntPtr", "System.IntPtr", false)] [InlineData("nint", "System.IntPtr", "System.IntPtr", "System.IntPtr", true)] [InlineData("nint", "System.IntPtr", "System.IntPtr", "System.IntPtr", false)] - [InlineData("System.IntPtr", "nint", "nint", "System.IntPtr", true)] - [InlineData("System.IntPtr", "nint", "nint", "System.IntPtr", false)] + [InlineData("System.IntPtr", "nint", "System.IntPtr", "System.IntPtr", true)] + [InlineData("System.IntPtr", "nint", "System.IntPtr", "System.IntPtr", false)] public void ExplicitImplementationInBaseType_02(string interfaceTypeArg, string baseTypeArg, string expectedTypeArg, string expectedOutput, bool useCompilationReference) { var source0 = @@ -2645,12 +2645,12 @@ static void Main() [InlineData("object", "dynamic", "dynamic", false)] [InlineData("(int X, int Y)", "(int X, int Y)", "(System.Int32 X, System.Int32 Y)", true)] [InlineData("(int X, int Y)", "(int X, int Y)", "(System.Int32 X, System.Int32 Y)", false)] - [InlineData("nint", "nint", "nint", true)] - [InlineData("nint", "nint", "nint", false)] + [InlineData("nint", "nint", "System.IntPtr", true)] + [InlineData("nint", "nint", "System.IntPtr", false)] [InlineData("nint", "System.IntPtr", "System.IntPtr", true)] [InlineData("nint", "System.IntPtr", "System.IntPtr", false)] - [InlineData("System.IntPtr", "nint", "nint", true)] - [InlineData("System.IntPtr", "nint", "nint", false)] + [InlineData("System.IntPtr", "nint", "System.IntPtr", true)] + [InlineData("System.IntPtr", "nint", "System.IntPtr", false)] public void ExplicitImplementationInBaseType_04(string interfaceTypeArg, string baseTypeArg, string expectedTypeArg, bool useCompilationReference) { var source0 = diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/UsingAliasTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/UsingAliasTests.cs index 7f8e0e1b28385..a6ef08cddf104 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/UsingAliasTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/UsingAliasTests.cs @@ -253,7 +253,7 @@ partial class A : Object {} [InlineData("int*", "System.Int32*")] [InlineData("delegate*", "delegate*")] [InlineData("dynamic", "dynamic")] - [InlineData("nint", "nint")] + [InlineData("nint", "System.IntPtr")] public void GetAliasTypeInfo(string aliasType, string expected) { // Should get the same results in the semantic model regardless of whether the using has the 'unsafe' diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs index 20f1a5586c8b8..04dc956174ed6 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs @@ -200,6 +200,7 @@ public class SymbolDisplayFormat compilerInternalOptions: SymbolDisplayCompilerInternalOptions.IncludeScriptType | SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames | + SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType | SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes | SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers | SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes); @@ -257,7 +258,7 @@ public class SymbolDisplayFormat miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.ExpandValueTuple, - compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames); + compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames | SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); /// /// Used to normalize explicit interface implementation member names. diff --git a/src/Compilers/Test/Core/CompilationVerifier.cs b/src/Compilers/Test/Core/CompilationVerifier.cs index d85b63ddf25ae..1571316a1a156 100644 --- a/src/Compilers/Test/Core/CompilationVerifier.cs +++ b/src/Compilers/Test/Core/CompilationVerifier.cs @@ -631,7 +631,7 @@ private IRuntimeEnvironment CreateRuntimeEnvironment(ModuleData mainModule, Immu } /// - /// Obsolete. Use instead. + /// Obsolete. Use instead. /// public CompilationVerifier VerifyIL( string qualifiedMethodName, @@ -641,11 +641,11 @@ public CompilationVerifier VerifyIL( [CallerFilePath] string? callerPath = null, [CallerLineNumber] int callerLine = 0) { - return VerifyILImpl(qualifiedMethodName, expectedIL.Value, realIL, sequencePoints: sequencePoints != null, sequencePointsSource: false, callerPath, callerLine, escapeQuotes: false); + return VerifyILImpl(qualifiedMethodName, expectedIL.Value, realIL, sequencePoints: sequencePoints != null, sequencePointsSource: false, callerPath, callerLine, escapeQuotes: false, ilFormat: null); } /// - /// Obsolete. Use instead. + /// Obsolete. Use instead. /// public CompilationVerifier VerifyIL( string qualifiedMethodName, @@ -654,9 +654,10 @@ public CompilationVerifier VerifyIL( string? sequencePoints = null, [CallerFilePath] string? callerPath = null, [CallerLineNumber] int callerLine = 0, - string? source = null) + string? source = null, + SymbolDisplayFormat? ilFormat = null) { - return VerifyILImpl(qualifiedMethodName, expectedIL, realIL, sequencePoints: sequencePoints != null, sequencePointsSource: source != null, callerPath, callerLine, escapeQuotes: false); + return VerifyILImpl(qualifiedMethodName, expectedIL, realIL, sequencePoints: sequencePoints != null, sequencePointsSource: source != null, callerPath, callerLine, escapeQuotes: false, ilFormat); } public CompilationVerifier VerifyMethodBody( @@ -664,9 +665,10 @@ public CompilationVerifier VerifyMethodBody( string expectedILWithSequencePoints, bool realIL = false, [CallerFilePath] string? callerPath = null, - [CallerLineNumber] int callerLine = 0) + [CallerLineNumber] int callerLine = 0, + SymbolDisplayFormat? ilFormat = null) { - return VerifyILImpl(qualifiedMethodName, expectedILWithSequencePoints, realIL, sequencePoints: true, sequencePointsSource: true, callerPath, callerLine, escapeQuotes: false); + return VerifyILImpl(qualifiedMethodName, expectedILWithSequencePoints, realIL, sequencePoints: true, sequencePointsSource: true, callerPath, callerLine, escapeQuotes: false, ilFormat); } public void VerifyILMultiple(params string[] qualifiedMethodNamesAndExpectedIL) @@ -725,17 +727,18 @@ private CompilationVerifier VerifyILImpl( bool sequencePointsSource, string? callerPath, int callerLine, - bool escapeQuotes) + bool escapeQuotes, + SymbolDisplayFormat? ilFormat) { - string? actualIL = VisualizeIL(qualifiedMethodName, realIL, sequencePoints, sequencePointsSource); + string? actualIL = VisualizeIL(qualifiedMethodName, realIL, sequencePoints, sequencePointsSource, ilFormat); AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, message: null, escapeQuotes, callerPath, callerLine); return this; } - public string VisualizeIL(string qualifiedMethodName, bool realIL = false, bool sequencePoints = false, bool sequencePointsSource = true) - => VisualizeIL(GetEmitData().TestData.GetMethodData(qualifiedMethodName), realIL, sequencePoints, sequencePointsSource); + public string VisualizeIL(string qualifiedMethodName, bool realIL = false, bool sequencePoints = false, bool sequencePointsSource = true, SymbolDisplayFormat? ilFormat = null) + => VisualizeIL(GetEmitData().TestData.GetMethodData(qualifiedMethodName), realIL, sequencePoints, sequencePointsSource, ilFormat); - internal string VisualizeIL(CompilationTestData.MethodData methodData, bool realIL = false, bool sequencePoints = false, bool sequencePointsSource = true) + internal string VisualizeIL(CompilationTestData.MethodData methodData, bool realIL = false, bool sequencePoints = false, bool sequencePointsSource = true, SymbolDisplayFormat? ilFormat = null) { Dictionary? markers = null; @@ -775,7 +778,7 @@ internal string VisualizeIL(CompilationTestData.MethodData methodData, bool real if (!realIL) { - return ILBuilderVisualizer.ILBuilderToString(methodData.ILBuilder, markers: markers); + return ILBuilderVisualizer.ILBuilderToString(methodData.ILBuilder, markers: markers, ilFormat: ilFormat); } if (_lazyModuleSymbol == null) diff --git a/src/Compilers/Test/Core/Metadata/ILBuilderVisualizer.cs b/src/Compilers/Test/Core/Metadata/ILBuilderVisualizer.cs index 39c539e512d61..b002e0c839cac 100644 --- a/src/Compilers/Test/Core/Metadata/ILBuilderVisualizer.cs +++ b/src/Compilers/Test/Core/Metadata/ILBuilderVisualizer.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -25,10 +23,12 @@ namespace Roslyn.Test.Utilities internal sealed class ILBuilderVisualizer : ILVisualizer { private readonly ITokenDeferral _tokenDeferral; + private readonly SymbolDisplayFormat _symbolDisplayFormat; - public ILBuilderVisualizer(ITokenDeferral tokenDeferral) + public ILBuilderVisualizer(ITokenDeferral tokenDeferral, SymbolDisplayFormat? symbolDisplayFormat = null) { _tokenDeferral = tokenDeferral; + _symbolDisplayFormat = symbolDisplayFormat ?? SymbolDisplayFormat.ILVisualizationFormat; } public override string VisualizeUserString(uint token) @@ -59,20 +59,20 @@ public override string VisualizeSymbol(uint token, OperandType operandType) } object reference = _tokenDeferral.GetReferenceFromToken(token); - ISymbol symbol = ((reference as ISymbolInternal) ?? (reference as Cci.IReference)?.GetInternalSymbol())?.GetISymbol(); - return string.Format("\"{0}\"", symbol == null ? (object)reference : symbol.ToDisplayString(SymbolDisplayFormat.ILVisualizationFormat)); + ISymbol? symbol = ((reference as ISymbolInternal) ?? (reference as Cci.IReference)?.GetInternalSymbol())?.GetISymbol(); + return string.Format("\"{0}\"", symbol == null ? (object)reference : symbol.ToDisplayString(_symbolDisplayFormat)); } - public override string VisualizeLocalType(object type) + public override string? VisualizeLocalType(object type) { - return (((type as ISymbolInternal) ?? (type as Cci.IReference)?.GetInternalSymbol()) is ISymbolInternal symbol) ? symbol.GetISymbol().ToDisplayString(SymbolDisplayFormat.ILVisualizationFormat) : type.ToString(); + return (((type as ISymbolInternal) ?? (type as Cci.IReference)?.GetInternalSymbol()) is ISymbolInternal symbol) ? symbol.GetISymbol().ToDisplayString(_symbolDisplayFormat) : type.ToString(); } /// /// Determine the list of spans ordered by handler /// block start, with outer handlers before inner. /// - private static List GetHandlerSpans(ImmutableArray regions) + private static List? GetHandlerSpans(ImmutableArray regions) { if (regions.IsDefaultOrEmpty) { @@ -136,8 +136,9 @@ private static List GetHandlerSpans(ImmutableArray internal static string ILBuilderToString( ILBuilder builder, - Func mapLocal = null, - IReadOnlyDictionary markers = null) + Func? mapLocal = null, + IReadOnlyDictionary? markers = null, + SymbolDisplayFormat? ilFormat = null) { var sb = new StringBuilder(); @@ -148,7 +149,7 @@ internal static string ILBuilderToString( } var locals = builder.LocalSlotManager.LocalsInOrder().SelectAsArray(mapLocal); - var visualizer = new ILBuilderVisualizer(builder.module); + var visualizer = new ILBuilderVisualizer(builder.module, ilFormat); if (!ilStream.IsDefault) { @@ -175,7 +176,7 @@ internal static string ILBuilderToString( internal static string LocalSignatureToString( ILBuilder builder, - Func mapLocal = null) + Func? mapLocal = null) { var sb = new StringBuilder(); From 278cf3da10689b09ef82adaa66608178970c6cc8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:36:38 -0700 Subject: [PATCH 086/114] Make IDiagAnalyzerService into a workspace service --- .../Analyzer/AnalyzerSettingsProvider.cs | 3 +- .../AnalyzerSettingsProviderFactory.cs | 3 +- ...AnalyzerSettingsWorkspaceServiceFactory.cs | 3 +- .../Suppression/SuppressionTests.cs | 2 +- .../Diagnostics/IDiagnosticAnalyzerService.cs | 3 +- .../Service/DiagnosticAnalyzerService.cs | 40 +++++++++++++++---- ...sticAnalyzerService_IncrementalAnalyzer.cs | 21 ---------- .../AbstractDocumentPullDiagnosticHandler.cs | 2 - .../AbstractPullDiagnosticHandler.cs | 2 - ...AbstractWorkspacePullDiagnosticsHandler.cs | 4 +- .../DocumentPullDiagnosticHandler.cs | 17 +++----- .../DocumentPullDiagnosticHandlerFactory.cs | 28 ++++--------- ...licDocumentPullDiagnosticHandlerFactory.cs | 28 ++++--------- .../PublicDocumentPullDiagnosticsHandler.cs | 21 ++++------ ...icWorkspacePullDiagnosticHandlerFactory.cs | 3 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 20 ++++------ .../WorkspacePullDiagnosticHandler.cs | 3 +- .../WorkspacePullDiagnosticHandlerFactory.cs | 3 +- 18 files changed, 80 insertions(+), 126 deletions(-) delete mode 100644 src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs index 26f5a7b35df73..36ae44f6e4def 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs @@ -24,11 +24,10 @@ public AnalyzerSettingsProvider( string fileName, AnalyzerSettingsUpdater settingsUpdater, Workspace workspace, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService optionService) : base(fileName, settingsUpdater, workspace, optionService) { - _analyzerService = analyzerService; + _analyzerService = workspace.Services.GetRequiredService(); Update(); } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs index 4a0dedbe75bb5..49de006cc6b58 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs @@ -11,12 +11,11 @@ namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyz internal sealed class AnalyzerSettingsProviderFactory( Workspace workspace, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptionService) : IWorkspaceSettingsProviderFactory { public ISettingsProvider GetForFile(string filePath) { var updater = new AnalyzerSettingsUpdater(workspace, filePath); - return new AnalyzerSettingsProvider(filePath, updater, workspace, analyzerService, globalOptionService); + return new AnalyzerSettingsProvider(filePath, updater, workspace, globalOptionService); } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs index 7945e987215b1..095d1a9d5624a 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs @@ -16,9 +16,8 @@ namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyz [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class AnalyzerSettingsWorkspaceServiceFactory( - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptionService) : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, analyzerService, globalOptionService); + => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, globalOptionService); } diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index d66b5c85e92b3..a678cd2e01cc5 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -446,7 +446,7 @@ void Method() var analyzerReference = new AnalyzerImageReference([new CSharpCompilerDiagnosticAnalyzer()]); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = workspace.ExportProvider.GetExportedValue(); + var diagnosticService = workspace.Services.GetRequiredService(); var suppressionProvider = CreateDiagnosticProviderAndFixer(workspace).Item2; var suppressionProviderFactory = new Lazy(() => suppressionProvider, new CodeChangeProviderMetadata("SuppressionProvider", languages: [LanguageNames.CSharp])); diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index f3bea05e7e868..77381fe91a7d8 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -7,11 +7,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Diagnostics; -internal interface IDiagnosticAnalyzerService +internal interface IDiagnosticAnalyzerService : IWorkspaceService { /// /// Provides and caches analyzer information. diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs index 58e7ec95e0a0e..2f9a0c90efe3f 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -19,7 +20,26 @@ namespace Microsoft.CodeAnalysis.Diagnostics; -[Export(typeof(IDiagnosticAnalyzerService)), Shared] +[ExportWorkspaceServiceFactory(typeof(IDiagnosticAnalyzerService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DiagnosticAnalyzerServiceFactory( + IGlobalOptionService globalOptions, + IDiagnosticsRefresher diagnosticsRefresher, + DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, + IAsynchronousOperationListenerProvider listenerProvider) : IWorkspaceServiceFactory +{ + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + return new DiagnosticAnalyzerService( + globalOptions, + diagnosticsRefresher, + globalCache, + listenerProvider, + workspaceServices.Workspace); + } +} + internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { private static readonly Option2 s_crashOnAnalyzerException = new("dotnet_crash_on_analyzer_exception", defaultValue: false); @@ -33,19 +53,18 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; private readonly IDiagnosticsRefresher _diagnosticsRefresher; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DiagnosticAnalyzerService( - IAsynchronousOperationListenerProvider listenerProvider, - DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, IGlobalOptionService globalOptions, - IDiagnosticsRefresher diagnosticsRefresher) + IDiagnosticsRefresher diagnosticsRefresher, + DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, + IAsynchronousOperationListenerProvider listenerProvider, + Workspace workspace) { AnalyzerInfoCache = globalCache.AnalyzerInfoCache; Listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService); GlobalOptions = globalOptions; _diagnosticsRefresher = diagnosticsRefresher; - _createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback; + _createIncrementalAnalyzer = _ => new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); globalOptions.AddOptionChangedHandler(this, (_, _, e) => { @@ -54,6 +73,10 @@ public DiagnosticAnalyzerService( RequestDiagnosticRefresh(); } }); + + // When the workspace changes what context a document is in (when a user picks a different tfm to view the + // document in), kick off a refresh so that diagnostics properly update in the task list and editor. + workspace.RegisterDocumentActiveContextChangedHandler(args => RequestDiagnosticRefresh()); } public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) @@ -77,6 +100,9 @@ public static bool IsGlobalOptionAffectingDiagnostics(IOption2 option) public void RequestDiagnosticRefresh() => _diagnosticsRefresher.RequestWorkspaceRefresh(); + private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) + => _map.GetValue(workspace, _createIncrementalAnalyzer); + public async Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs deleted file mode 100644 index 74a5ef511712f..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ /dev/null @@ -1,21 +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. - -namespace Microsoft.CodeAnalysis.Diagnostics; - -internal sealed partial class DiagnosticAnalyzerService -{ - private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - { - return _map.GetValue(workspace, _createIncrementalAnalyzer); - } - - private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspace workspace) - { - // subscribe to active context changed event for new workspace - _ = workspace.RegisterDocumentActiveContextChangedHandler(args => RequestDiagnosticRefresh()); - - return new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); - } -} diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 106d2dc6bfc78..21c18891297be 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -14,12 +14,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( - diagnosticAnalyzerService, diagnosticRefresher, globalOptions), ITextDocumentIdentifierHandler where TDiagnosticsParams : IPartialResultParams diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index d1f6ead2e0d06..bd4cba8018918 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -26,7 +26,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// The LSP type that is reported via IProgress /// The LSP type that is returned on completion of the request. internal abstract partial class AbstractPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : ILspServiceRequestHandler @@ -43,7 +42,6 @@ internal abstract partial class AbstractPullDiagnosticHandler /// Map of diagnostic category to the diagnostics cache for that category. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 19e0db35a9214..49e0e049d4295 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -37,10 +37,10 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler +internal sealed partial class DocumentPullDiagnosticHandler( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractDocumentPullDiagnosticHandler( + diagnosticRefresher, diagnosticSourceManager, globalOptions) { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) - { - } - protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index 07a6cfcaabc17..860cf5707127b 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -12,27 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [ExportCSharpVisualBasicLspServiceFactory(typeof(DocumentPullDiagnosticHandler)), Shared] -internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class DocumentPullDiagnosticHandlerFactory( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly IDiagnosticsRefresher _diagnosticsRefresher; - private readonly IGlobalOptionService _globalOptions; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - { - _analyzerService = analyzerService; - _diagnosticSourceManager = diagnosticSourceManager; - _diagnosticsRefresher = diagnosticsRefresher; - _globalOptions = globalOptions; - } - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(diagnosticSourceManager, diagnosticsRefresher, globalOptions); } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index 3ae460c780e84..296c73df037a6 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -15,30 +15,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic [ExportCSharpVisualBasicLspServiceFactory(typeof(PublicDocumentPullDiagnosticsHandler)), Shared] -internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentPullDiagnosticHandlerFactory( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly IDiagnosticsRefresher _diagnosticRefresher; - private readonly IGlobalOptionService _globalOptions; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PublicDocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - { - _analyzerService = analyzerService; - _diagnosticSourceManager = diagnosticSourceManager; - _diagnosticRefresher = diagnosticRefresher; - _globalOptions = globalOptions; - } - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, diagnosticSourceManager, diagnosticRefresher, globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 877ecb816e1a0..bc806ef8c5dd7 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -18,20 +18,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler +internal sealed partial class PublicDocumentPullDiagnosticsHandler( + IClientLanguageServerManager clientLanguageServerManager, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) + : AbstractDocumentPullDiagnosticHandler( + diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - private readonly IClientLanguageServerManager _clientLanguageServerManager; - - public PublicDocumentPullDiagnosticsHandler( - IClientLanguageServerManager clientLanguageServerManager, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) - { - _clientLanguageServerManager = clientLanguageServerManager; - } + private readonly IClientLanguageServerManager _clientLanguageServerManager = clientLanguageServerManager; protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index ea0814316fbfc..fd8fd73e460fc 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 8b375e0059b3e..31eab76acec0a 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -20,19 +20,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler( + LspWorkspaceManager workspaceManager, + LspWorkspaceRegistrationService registrationService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractWorkspacePullDiagnosticsHandler( + workspaceManager, registrationService, diagnosticSourceManager, diagnosticRefresher, globalOptions), IDisposable { - public PublicWorkspacePullDiagnosticsHandler( - LspWorkspaceManager workspaceManager, - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) - { - } - protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 05b60afae44a9..9710bd9a508ed 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -17,12 +17,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics // produced by document diagnostics. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index 7c13f4d46b74a..9204ca4f9b451 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal class WorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } From 00680203f4d4a33dfcb847a29291eb6715cf8ecd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:38:21 -0700 Subject: [PATCH 087/114] Remove unnecessary dependency in LSP handlers --- .../AbstractDocumentPullDiagnosticHandler.cs | 2 -- .../AbstractPullDiagnosticHandler.cs | 2 -- ...AbstractWorkspacePullDiagnosticsHandler.cs | 4 +-- .../DocumentPullDiagnosticHandler.cs | 17 ++++------- .../DocumentPullDiagnosticHandlerFactory.cs | 28 +++++-------------- ...licDocumentPullDiagnosticHandlerFactory.cs | 28 +++++-------------- .../PublicDocumentPullDiagnosticsHandler.cs | 21 ++++++-------- ...icWorkspacePullDiagnosticHandlerFactory.cs | 3 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 20 ++++++------- .../WorkspacePullDiagnosticHandler.cs | 3 +- .../WorkspacePullDiagnosticHandlerFactory.cs | 3 +- 11 files changed, 41 insertions(+), 90 deletions(-) diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 106d2dc6bfc78..21c18891297be 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -14,12 +14,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( - diagnosticAnalyzerService, diagnosticRefresher, globalOptions), ITextDocumentIdentifierHandler where TDiagnosticsParams : IPartialResultParams diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index d1f6ead2e0d06..bd4cba8018918 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -26,7 +26,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// The LSP type that is reported via IProgress /// The LSP type that is returned on completion of the request. internal abstract partial class AbstractPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : ILspServiceRequestHandler @@ -43,7 +42,6 @@ internal abstract partial class AbstractPullDiagnosticHandler /// Map of diagnostic category to the diagnostics cache for that category. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 19e0db35a9214..49e0e049d4295 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -37,10 +37,10 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler +internal sealed partial class DocumentPullDiagnosticHandler( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractDocumentPullDiagnosticHandler( + diagnosticRefresher, diagnosticSourceManager, globalOptions) { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) - { - } - protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index 07a6cfcaabc17..860cf5707127b 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -12,27 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [ExportCSharpVisualBasicLspServiceFactory(typeof(DocumentPullDiagnosticHandler)), Shared] -internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class DocumentPullDiagnosticHandlerFactory( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly IDiagnosticsRefresher _diagnosticsRefresher; - private readonly IGlobalOptionService _globalOptions; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - { - _analyzerService = analyzerService; - _diagnosticSourceManager = diagnosticSourceManager; - _diagnosticsRefresher = diagnosticsRefresher; - _globalOptions = globalOptions; - } - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(diagnosticSourceManager, diagnosticsRefresher, globalOptions); } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index 3ae460c780e84..296c73df037a6 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -15,30 +15,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic [ExportCSharpVisualBasicLspServiceFactory(typeof(PublicDocumentPullDiagnosticsHandler)), Shared] -internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentPullDiagnosticHandlerFactory( + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly IDiagnosticsRefresher _diagnosticRefresher; - private readonly IGlobalOptionService _globalOptions; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public PublicDocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - { - _analyzerService = analyzerService; - _diagnosticSourceManager = diagnosticSourceManager; - _diagnosticRefresher = diagnosticRefresher; - _globalOptions = globalOptions; - } - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, diagnosticSourceManager, diagnosticRefresher, globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 877ecb816e1a0..bc806ef8c5dd7 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -18,20 +18,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler +internal sealed partial class PublicDocumentPullDiagnosticsHandler( + IClientLanguageServerManager clientLanguageServerManager, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) + : AbstractDocumentPullDiagnosticHandler( + diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - private readonly IClientLanguageServerManager _clientLanguageServerManager; - - public PublicDocumentPullDiagnosticsHandler( - IClientLanguageServerManager clientLanguageServerManager, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) - { - _clientLanguageServerManager = clientLanguageServerManager; - } + private readonly IClientLanguageServerManager _clientLanguageServerManager = clientLanguageServerManager; protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index ea0814316fbfc..fd8fd73e460fc 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 8b375e0059b3e..31eab76acec0a 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -20,19 +20,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler( + LspWorkspaceManager workspaceManager, + LspWorkspaceRegistrationService registrationService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractWorkspacePullDiagnosticsHandler( + workspaceManager, registrationService, diagnosticSourceManager, diagnosticRefresher, globalOptions), IDisposable { - public PublicWorkspacePullDiagnosticsHandler( - LspWorkspaceManager workspaceManager, - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) - { - } - protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 05b60afae44a9..9710bd9a508ed 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -17,12 +17,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics // produced by document diagnostics. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index 7c13f4d46b74a..9204ca4f9b451 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal class WorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } From 668d57ea6910d3d197012e502b43647d9be384ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:41:14 -0700 Subject: [PATCH 088/114] Downstream --- .../VSTypeScriptPullDiagnosticHandlerProvider.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index b5527e89596a0..5cfbbe11205b6 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -17,21 +17,17 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) -{ -} + IGlobalOptionService globalOptions) + : DocumentPullDiagnosticHandlerFactory(diagnosticSourceManager, diagnosticsRefresher, globalOptions); [ExportLspServiceFactory(typeof(WorkspacePullDiagnosticHandler), ProtocolConstants.TypeScriptLanguageContract), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) -{ -} + IGlobalOptionService globalOptions) + : WorkspacePullDiagnosticHandlerFactory(registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); From 4e46fdd5bfc1da980934a66ed3fd9064e1976e28 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:48:27 -0700 Subject: [PATCH 089/114] Remove more dependencies --- .../RenameTrackingTaggerProvider.StateMachine.cs | 7 ++----- .../Core/RenameTracking/RenameTrackingTaggerProvider.cs | 4 +--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs index 33e6adf031c9b..960c6d7e229c1 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs @@ -38,7 +38,6 @@ private sealed class StateMachine private readonly IInlineRenameService _inlineRenameService; private readonly IAsynchronousOperationListener _asyncListener; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; // Store committed sessions so they can be restored on undo/redo. The undo transactions // may live beyond the lifetime of the buffer tracked by this StateMachine, so storing @@ -58,7 +57,6 @@ public StateMachine( IThreadingContext threadingContext, ITextBuffer buffer, IInlineRenameService inlineRenameService, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IGlobalOptionService globalOptions, IAsynchronousOperationListener asyncListener) { @@ -67,7 +65,6 @@ public StateMachine( Buffer.Changed += Buffer_Changed; _inlineRenameService = inlineRenameService; _asyncListener = asyncListener; - _diagnosticAnalyzerService = diagnosticAnalyzerService; GlobalOptions = globalOptions; } @@ -238,8 +235,8 @@ public bool ClearVisibleTrackingSession() // provide a diagnostic/codefix, but nothing has changed in the workspace // to trigger the diagnostic system to reanalyze, so we trigger it // manually. - - _diagnosticAnalyzerService?.RequestDiagnosticRefresh(); + var service = document.Project.Solution.Services.GetRequiredService(); + service.RequestDiagnosticRefresh(); } // Disallow the existing TrackingSession from triggering IdentifierFound. diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs index 8e4deef024bea..4fb1de3d67acc 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs @@ -40,19 +40,17 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking; internal sealed partial class RenameTrackingTaggerProvider( IThreadingContext threadingContext, IInlineRenameService inlineRenameService, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IGlobalOptionService globalOptions, IAsynchronousOperationListenerProvider listenerProvider) : ITaggerProvider { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.RenameTracking); private readonly IInlineRenameService _inlineRenameService = inlineRenameService; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; private readonly IGlobalOptionService _globalOptions = globalOptions; public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { - var stateMachine = buffer.Properties.GetOrCreateSingletonProperty(() => new StateMachine(_threadingContext, buffer, _inlineRenameService, _diagnosticAnalyzerService, _globalOptions, _asyncListener)); + var stateMachine = buffer.Properties.GetOrCreateSingletonProperty(() => new StateMachine(_threadingContext, buffer, _inlineRenameService, _globalOptions, _asyncListener)); return new Tagger(stateMachine) as ITagger; } From 163e9f6000eeb9792b3f1c27fbbada39d8216fb8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:57:34 -0700 Subject: [PATCH 090/114] Remove more dependencies --- .../Test/CodeFixes/CodeFixServiceTests.cs | 12 ++++++------ .../Diagnostics/DiagnosticAnalyzerServiceTests.cs | 4 +--- .../Suppression/VisualStudioSuppressionFixService.cs | 10 +++++----- .../TaskList/ExternalErrorDiagnosticUpdateSource.cs | 5 +---- src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs | 2 +- .../BaseDiagnosticAndGeneratorItemSource.cs | 6 ++---- .../DiagnosticItem/CpsDiagnosticItemSource.cs | 3 +-- .../CpsDiagnosticItemSourceProvider.cs | 4 +--- .../DiagnosticItem/LegacyDiagnosticItemSource.cs | 2 -- .../LegacyDiagnosticItemSourceProvider.cs | 3 +-- .../ExternalDiagnosticUpdateSourceTests.vb | 2 +- .../SolutionExplorer/SourceGeneratorItemTests.vb | 2 +- .../Test/Venus/DocumentService_IntegrationTests.vb | 2 +- 13 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 90505c61bc382..0bb022598afc4 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -43,7 +43,7 @@ public async Task TestGetFirstDiagnosticWithFixAsync() "; using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); @@ -323,7 +323,7 @@ private static async Task GetFirstDiagnosticWithFixWithExceptionValidationAsync( Assert.True(errorReported); } - private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( CodeFixProvider codefix, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -331,7 +331,7 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz string code = "class Program { }") => ServiceSetup([codefix], includeConfigurationFixProviders, throwExceptionInFixerCreation, additionalDocument, code); - private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( ImmutableArray codefixers, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -355,7 +355,7 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => new TestErrorLogger())); var errorLogger = logger.First().Value; @@ -369,7 +369,7 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz fixers, configurationFixProviders); - return (workspace, diagnosticService, fixService, errorLogger); + return (workspace, fixService, errorLogger); } private static void GetDocumentAndExtensionManager( @@ -1033,7 +1033,6 @@ void M() var tuple = ServiceSetup(codeFix, code: code); using var workspace = tuple.workspace; - var analyzerService = tuple.analyzerService; GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); @@ -1047,6 +1046,7 @@ void M() // We enable full solution analysis so the 'AnalyzeDocumentAsync' doesn't skip analysis based on whether the document is active/open. workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); + var analyzerService = workspace.Services.GetRequiredService(); var diagnostics = await analyzerService.ForceAnalyzeProjectAsync(sourceDocument.Project, CancellationToken.None); await VerifyCachedDiagnosticsAsync( sourceDocument, expectedCachedDiagnostic: diagnosticOnFixLineInPriorSnapshot, testSpan, diagnostics); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index b77b1d991663b..cbc369da7c564 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -61,9 +61,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var document = GetDocumentFromIncompleteProject(workspace); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); - var globalOptions = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.GetDiagnosticsForIdsAsync( workspace.CurrentSolution.Projects.Single(), documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index 99b8b555afe84..2484e4cba2f58 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -42,7 +42,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Suppression; internal sealed class VisualStudioSuppressionFixService( IThreadingContext threadingContext, VisualStudioWorkspaceImpl workspace, - IDiagnosticAnalyzerService diagnosticService, ICodeFixService codeFixService, ICodeActionEditHandlerService editHandlerService, VisualStudioDiagnosticListSuppressionStateService suppressionStateService, @@ -53,7 +52,6 @@ internal sealed class VisualStudioSuppressionFixService( private readonly IThreadingContext _threadingContext = threadingContext; private readonly VisualStudioWorkspaceImpl _workspace = workspace; private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.ErrorList); - private readonly IDiagnosticAnalyzerService _diagnosticService = diagnosticService; private readonly ICodeFixService _codeFixService = codeFixService; private readonly IFixMultipleOccurrencesService _fixMultipleOccurencesService = workspace.Services.GetRequiredService(); private readonly ICodeActionEditHandlerService _editHandlerService = editHandlerService; @@ -348,7 +346,7 @@ await _editHandlerService.ApplyAsync( cancellationToken).ConfigureAwait(false); // Kick off diagnostic re-analysis for affected projects so that diagnostics gets refreshed. - _diagnosticService.RequestDiagnosticRefresh(); + _workspace.Services.GetRequiredService().RequestDiagnosticRefresh(); } } catch (OperationCanceledException) @@ -484,7 +482,8 @@ private async Task>> Ge RoslynDebug.AssertNotNull(latestDocumentDiagnosticsMap); var uniqueDiagnosticIds = group.SelectMany(kvp => kvp.Value.Select(d => d.Id)).ToImmutableHashSet(); - var latestProjectDiagnostics = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, + var diagnosticService = _workspace.Services.GetRequiredService(); + var latestProjectDiagnostics = (await diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)).Where(IsDocumentDiagnostic); @@ -562,7 +561,8 @@ private async Task>> Get RoslynDebug.AssertNotNull(latestDiagnosticsToFix); var uniqueDiagnosticIds = diagnostics.Select(d => d.Id).ToImmutableHashSet(); - var latestDiagnosticsFromDiagnosticService = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, + var diagnosticService = _workspace.Services.GetRequiredService(); + var latestDiagnosticsFromDiagnosticService = (await diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)); diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 7fd735634876b..5a90fc40c77fe 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -38,7 +38,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable { private readonly Workspace _workspace; - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly IAsynchronousOperationListener _listener; private readonly CancellationToken _disposalToken; private readonly IServiceBroker _serviceBroker; @@ -65,14 +64,12 @@ internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public ExternalErrorDiagnosticUpdateSource( VisualStudioWorkspace workspace, - IDiagnosticAnalyzerService diagnosticService, IAsynchronousOperationListenerProvider listenerProvider, [Import(typeof(SVsFullAccessServiceBroker))] IServiceBroker serviceBroker, IThreadingContext threadingContext) { _disposalToken = threadingContext.DisposalToken; _workspace = workspace; - _diagnosticService = diagnosticService; _listener = listenerProvider.GetListener(FeatureAttribute.ErrorList); _serviceBroker = serviceBroker; @@ -90,7 +87,7 @@ private async ValueTask ProcessTaskQueueItemsAsync(ImmutableSegmentedList _diagnosticService.AnalyzerInfoCache; + public DiagnosticAnalyzerInfoCache AnalyzerInfoCache => this._workspace.Services.GetRequiredService().AnalyzerInfoCache; public void Dispose() { diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index cf11ec5068991..89ee84515afff 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs @@ -80,7 +80,7 @@ internal ContainedLanguage( Workspace = workspace; _editorAdaptersFactoryService = componentModel.GetService(); - _diagnosticAnalyzerService = componentModel.GetService(); + _diagnosticAnalyzerService = workspace.Services.GetRequiredService(); // Get the ITextBuffer for the secondary buffer Marshal.ThrowExceptionForHR(bufferCoordinator.GetSecondaryBuffer(out var secondaryTextLines)); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs index 2197f03fa4d48..cdaeba7816eb8 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs @@ -26,7 +26,6 @@ internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttached { private static readonly DiagnosticDescriptorComparer s_comparer = new(); - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; private readonly BulkObservableCollection _items = []; private readonly CancellationTokenSource _cancellationTokenSource = new(); @@ -48,14 +47,12 @@ public BaseDiagnosticAndGeneratorItemSource( Workspace workspace, ProjectId projectId, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; Workspace = workspace; ProjectId = projectId; CommandHandler = commandHandler; - _diagnosticAnalyzerService = diagnosticAnalyzerService; _workQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.Idle, @@ -152,8 +149,9 @@ ImmutableArray GenerateDiagnosticItems( var specificDiagnosticOptions = project.CompilationOptions!.SpecificDiagnosticOptions; var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); + var diagnosticAnalyzerService = this.Workspace.Services.GetRequiredService(); return analyzerReference.GetAnalyzers(project.Language) - .SelectMany(a => _diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a)) + .SelectMany(a => diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a)) .GroupBy(d => d.Id) .OrderBy(g => g.Key, StringComparer.CurrentCulture) .SelectAsArray(g => diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs index 2d7c2aa84cc51..c36d8a75123d4 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs @@ -29,9 +29,8 @@ public CpsDiagnosticItemSource( ProjectId projectId, IVsHierarchyItem item, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService analyzerService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, workspace, projectId, commandHandler, analyzerService, listenerProvider) + : base(threadingContext, workspace, projectId, commandHandler, listenerProvider) { _item = item; _projectDirectoryPath = Path.GetDirectoryName(projectPath); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs index 6983a954d3f69..75b02e1e51ea6 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs @@ -30,14 +30,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore internal sealed class CpsDiagnosticItemSourceProvider( IThreadingContext threadingContext, [Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, VisualStudioWorkspace workspace, IAsynchronousOperationListenerProvider listenerProvider) : AttachedCollectionSourceProvider { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IAnalyzersCommandHandler _commandHandler = commandHandler; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; private readonly Workspace _workspace = workspace; private readonly IAsynchronousOperationListenerProvider _listenerProvider = listenerProvider; @@ -63,7 +61,7 @@ internal sealed class CpsDiagnosticItemSourceProvider( if (hierarchy.GetCanonicalName(itemId, out var projectCanonicalName) == VSConstants.S_OK) { return new CpsDiagnosticItemSource( - _threadingContext, _workspace, projectCanonicalName, projectId, item, _commandHandler, _diagnosticAnalyzerService, _listenerProvider); + _threadingContext, _workspace, projectCanonicalName, projectId, item, _commandHandler, _listenerProvider); } } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs index e88a2616faac6..0fe084b978786 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs @@ -16,14 +16,12 @@ public LegacyDiagnosticItemSource( IThreadingContext threadingContext, AnalyzerItem item, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) : base( threadingContext, item.AnalyzersFolder.Workspace, item.AnalyzersFolder.ProjectId, commandHandler, - diagnosticAnalyzerService, listenerProvider) { _item = item; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs index f0513880d775f..b11b690e091d7 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs @@ -23,7 +23,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore internal sealed class LegacyDiagnosticItemSourceProvider( IThreadingContext threadingContext, [Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) : AttachedCollectionSourceProvider { protected override IAttachedCollectionSource? CreateCollectionSource(AnalyzerItem item, string relationshipName) @@ -31,7 +30,7 @@ internal sealed class LegacyDiagnosticItemSourceProvider( if (relationshipName == KnownRelationships.Contains) { return new LegacyDiagnosticItemSource( - threadingContext, item, commandHandler, diagnosticAnalyzerService, listenerProvider); + threadingContext, item, commandHandler, listenerProvider); } return null; diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index f6e7a31817755..97db0fb8e9440 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -233,7 +233,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Dim service = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim service = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 7074c37fd513b..176b2cc3a8112 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -272,7 +272,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer workspace.GetService(Of IThreadingContext), New AnalyzerItem(New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, projectId, Nothing, Nothing), analyzerReference, Nothing), New FakeAnalyzersCommandHandler, - workspace.GetService(Of IDiagnosticAnalyzerService), + workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService), workspace.GetService(Of IAsynchronousOperationListenerProvider)) End Function diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 38be0fb67008c..2aec249ad56ae 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -229,7 +229,7 @@ class { } ' confirm there are errors Assert.True(model.GetDiagnostics().Any()) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' confirm diagnostic support is off for the document Assert.False(document.SupportsDiagnostics()) From 04189c4ebbc2bd3f4b5568f649035ae4c4560b1b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 12:58:21 -0700 Subject: [PATCH 091/114] Remove more dependencies --- .../VisualBasicCodeCleanupService.vb | 4 ++-- .../CodeCleanup/AbstractCodeCleanupService.cs | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb index c23580a6824f3..f0c150f02a1e7 100644 --- a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb +++ b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb @@ -82,8 +82,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeCleanup - Public Sub New(codeFixService As ICodeFixService, diagnosticAnalyzerService As IDiagnosticAnalyzerService) - MyBase.New(codeFixService, diagnosticAnalyzerService) + Public Sub New(codeFixService As ICodeFixService) + MyBase.New(codeFixService) End Sub Protected Overrides ReadOnly Property OrganizeImportsDescription As String = VBFeaturesResources.Organize_Imports diff --git a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs index 886cfa811f36a..9ab56fa471c33 100644 --- a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs +++ b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs @@ -21,16 +21,9 @@ namespace Microsoft.CodeAnalysis.CodeCleanup; -internal abstract class AbstractCodeCleanupService : ICodeCleanupService +internal abstract class AbstractCodeCleanupService(ICodeFixService codeFixService) : ICodeCleanupService { - private readonly ICodeFixService _codeFixService; - private readonly IDiagnosticAnalyzerService _diagnosticService; - - protected AbstractCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) - { - _codeFixService = codeFixService; - _diagnosticService = diagnosticAnalyzerService; - } + private readonly ICodeFixService _codeFixService = codeFixService; protected abstract string OrganizeImportsDescription { get; } protected abstract ImmutableArray GetDiagnosticSets(); @@ -194,13 +187,15 @@ private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( return solution.GetDocument(document.Id) ?? throw new NotSupportedException(FeaturesResources.Removal_of_document_not_supported); } - private async Task> GetThirdPartyDiagnosticIdsAndTitlesAsync(Document document, CancellationToken cancellationToken) + private async Task> GetThirdPartyDiagnosticIdsAndTitlesAsync( + Document document, CancellationToken cancellationToken) { var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var range = new TextSpan(0, tree.Length); // Compute diagnostics for everything that is not an IDE analyzer - var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, + var diagnosticService = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = await diagnosticService.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, From 19444fd34c3d6db2b12c5bf9abdecd09c9563c00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:02:27 -0700 Subject: [PATCH 092/114] Remove more dependencies --- .../TestDiagnosticAnalyzerDriver.cs | 4 +--- .../AbstractDocumentDiagnosticSource.cs | 1 + ...bstractWorkspaceDocumentDiagnosticSource.cs | 18 ++++++++++-------- .../DocumentDiagnosticSource.cs | 5 +++-- .../NonLocalDocumentDiagnosticSource.cs | 6 ++++-- ...DocumentNonLocalDiagnosticSourceProvider.cs | 5 ++--- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 7ecb73fc3ad04..7f2f6b4c1b0a7 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -25,9 +25,7 @@ public sealed class TestDiagnosticAnalyzerDriver public TestDiagnosticAnalyzerDriver(Workspace workspace, bool includeSuppressedDiagnostics = false, bool includeNonLocalDocumentDiagnostics = false) { - var mefServices = workspace.Services.SolutionServices.ExportProvider; - - _diagnosticAnalyzerService = mefServices.GetExportedValue(); + _diagnosticAnalyzerService = workspace.Services.GetRequiredService(); _includeSuppressedDiagnostics = includeSuppressedDiagnostics; _includeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index f88810864a7bb..03a37330ae190 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -14,6 +14,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do where TDocument : TextDocument { public TDocument Document { get; } = document; + public Solution Solution => this.Document.Project.Solution; public abstract bool IsLiveSource(); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 7777dba417737..9e0a50253403e 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -15,13 +15,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource( + TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -44,20 +45,20 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + var service = this.Solution.Services.GetRequiredService(); + var documentDiagnostics = await service.GetDiagnosticsForSpanAsync( sourceGeneratedDocument, range: null, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); documentDiagnostics = documentDiagnostics.WhereAsArray(d => !d.IsSuppressed); return documentDiagnostics; } else { - var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + var projectDiagnostics = await GetProjectDiagnosticsAsync(cancellationToken).ConfigureAwait(false); return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); } } - private async ValueTask> GetProjectDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + private async ValueTask> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) { if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) { @@ -75,7 +76,8 @@ AsyncLazy> GetLazyDiagnostics() _ => AsyncLazy.Create( async cancellationToken => { - var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var allDiagnostics = await service.GetDiagnosticsForIdsAsync( Document.Project, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 34203462a17d3..9960d4dc5a7e5 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) +internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -27,7 +27,8 @@ public override async Task> GetDiagnosticsAsync( // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas // GetDiagnosticsForSpanAsync will only run analyzers against the request document. - var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + var service = this.Solution.Services.GetRequiredService(); + var allSpanDiagnostics = await service.GetDiagnosticsForSpanAsync( Document, range: null, diagnosticKind: this.DiagnosticKind, cancellationToken).ConfigureAwait(false); // Note: we do not filter our suppressed diagnostics we we want unnecessary suppressions to be reported. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index b089870386125..2551c6592a82b 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource( + TextDocument document, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -25,7 +26,8 @@ public override async Task> GetDiagnosticsAsync( // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of non-local diagnostics for this // document including those reported as a compilation end diagnostic. These are not included in document pull // (uses GetDiagnosticsForSpan) due to cost. - var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var diagnostics = await service.GetDiagnosticsForIdsAsync( Document.Project, Document.Id, diagnosticIds: null, _shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 5ac98fefc7389..064d4761e82ab 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -19,8 +19,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + [Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { public const string NonLocal = nameof(NonLocal); @@ -36,7 +35,7 @@ public ValueTask> CreateDiagnosticSourcesAsync globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) == BackgroundAnalysisScope.FullSolution) { // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer())]); + return new([new NonLocalDocumentDiagnosticSource(textDocument, a => !a.IsCompilerAnalyzer())]); } return new([]); From d1982ae6ec5d0b90cf37e278e9d40f8a04552696 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:03:44 -0700 Subject: [PATCH 093/114] Remove more dependencies --- ...spaceDocumentsAndProjectDiagnosticSourceProvider.cs | 9 ++++----- .../AbstractProjectDiagnosticSource.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 4517be16a7084..d6f95565292b7 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -21,7 +21,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, [Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { @@ -58,11 +57,11 @@ public async ValueTask> CreateDiagnosticSource var codeAnalysisService = solution.Services.GetRequiredService(); foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); return result.ToImmutableAndClear(); - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) { var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) @@ -90,7 +89,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } @@ -100,7 +99,7 @@ void AddDocumentSources(IEnumerable documents) void AddProjectSource() { var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); result.Add(projectDiagnosticSource); } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index 18fc22f98ca51..d814da265f1d5 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -15,9 +15,10 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) : IDiagnosticSource { protected Project Project => project; + protected Solution Solution => this.Project.Solution; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); @@ -33,7 +34,7 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) { /// @@ -50,7 +51,8 @@ public override async Task> GetDiagnosticsAsync( // we're passing in. If information is already cached for that snapshot, it will be returned. Otherwise, // it will be computed on demand. Because it is always accurate as per this snapshot, all spans are correct // and do not need to be adjusted. - var diagnostics = await diagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var diagnostics = await service.GetProjectDiagnosticsForIdsAsync( Project, diagnosticIds: null, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false); // TODO(cyrusn): In the future we could consider reporting these, but with a flag on the diagnostic mentioning From c4dec8f23fc3a35504fb8cb1ad07a56ee16e3e38 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:04:33 -0700 Subject: [PATCH 094/114] Remove more dependencies --- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 85fca7423bdd3..3216b53f46d1d 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( - IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind kind, string sourceName) + DiagnosticKind kind, string sourceName) : IDiagnosticSourceProvider { public bool IsDocument => true; @@ -26,7 +26,7 @@ public ValueTask> CreateDiagnosticSourcesAsync { if (context.GetTrackedDocument() is { } document) { - return new([new DocumentDiagnosticSource(diagnosticAnalyzerService, kind, document)]); + return new([new DocumentDiagnosticSource(kind, document)]); } return new([]); @@ -35,8 +35,8 @@ public ValueTask> CreateDiagnosticSourcesAsync [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) { } @@ -44,17 +44,17 @@ private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDi [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + private sealed class DocumentCompilerSemanticDiagnosticSourceProvider() : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( - diagnosticAnalyzerService, DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) { } [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) { } @@ -62,8 +62,8 @@ private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDi [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) { } From 36d0b6fde9b47f2921d0d601016c7ba1eda7fddd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:13:08 -0700 Subject: [PATCH 095/114] Remove more dependencies --- .../Copilot/ICopilotChangeAnalysisService.cs | 4 +--- .../CodeAnalysisDiagnosticAnalyzerService.cs | 8 ++------ .../VSTypeScriptDiagnosticAnalyzerService.cs | 15 ++++++--------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs index fd2849e1dd6d1..49074de6cb127 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -33,12 +33,10 @@ Task AnalyzeChangeAsync( [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultCopilotChangeAnalysisService( - [Import(AllowDefault = true)] ICodeFixService? codeFixService = null, - [Import(AllowDefault = true)] IDiagnosticAnalyzerService? diagnosticAnalyzerService = null) : ICopilotChangeAnalysisService + [Import(AllowDefault = true)] ICodeFixService? codeFixService = null) : ICopilotChangeAnalysisService { #pragma warning disable IDE0052 // Remove unread private members private readonly ICodeFixService? _codeFixService = codeFixService; - private readonly IDiagnosticAnalyzerService? _diagnosticAnalyzerService = diagnosticAnalyzerService; #pragma warning restore IDE0052 // Remove unread private members public async Task AnalyzeChangeAsync( diff --git a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs index 2ae365daf1c0e..53c1f6c66f614 100644 --- a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs @@ -23,10 +23,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed class CodeAnalysisDiagnosticAnalyzerServiceFactory() : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - var diagnosticAnalyzerService = workspaceServices.SolutionServices.ExportProvider.GetExports().Single().Value; - return new CodeAnalysisDiagnosticAnalyzerService(diagnosticAnalyzerService, workspaceServices.Workspace); - } + => new CodeAnalysisDiagnosticAnalyzerService(workspaceServices.Workspace); private sealed class CodeAnalysisDiagnosticAnalyzerService : ICodeAnalysisDiagnosticAnalyzerService { @@ -50,11 +47,10 @@ private sealed class CodeAnalysisDiagnosticAnalyzerService : ICodeAnalysisDiagno private readonly ConcurrentSet _clearedProjectIds = []; public CodeAnalysisDiagnosticAnalyzerService( - IDiagnosticAnalyzerService diagnosticAnalyzerService, Workspace workspace) { - _diagnosticAnalyzerService = diagnosticAnalyzerService; _workspace = workspace; + _diagnosticAnalyzerService = _workspace.Services.GetRequiredService(); _workspace.WorkspaceChanged += OnWorkspaceChanged; } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs index 305b32beb9fdd..7f5f386c57b96 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs @@ -3,22 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using System.Collections.Generic; +using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; -[Shared] -[Export(typeof(IVSTypeScriptDiagnosticAnalyzerService))] +[Export(typeof(IVSTypeScriptDiagnosticAnalyzerService)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class VSTypeScriptAnalyzerService(IDiagnosticAnalyzerService service) : IVSTypeScriptDiagnosticAnalyzerService +internal sealed class VSTypeScriptAnalyzerService(IDiagnosticsRefresher refresher) : IVSTypeScriptDiagnosticAnalyzerService { - private readonly IDiagnosticAnalyzerService _service = service; - public void Reanalyze(Workspace? workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority) - => _service.RequestDiagnosticRefresh(); + => refresher.RequestWorkspaceRefresh(); } From 8ff17fd904b2654e33bc77fd2c3c018fc38570c0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:16:08 -0700 Subject: [PATCH 096/114] Remove more dependencies --- ...CodeFixService.FixAllDiagnosticProvider.cs | 18 ++++++++------- .../CodeFixes/Service/CodeFixService.cs | 22 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs index 147c4bd50893c..5b647abe2078d 100644 --- a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs @@ -18,14 +18,11 @@ internal sealed partial class CodeFixService { private sealed class FixAllDiagnosticProvider : FixAllContext.SpanBasedDiagnosticProvider { - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableHashSet? _diagnosticIds; private readonly bool _includeSuppressedDiagnostics; - public FixAllDiagnosticProvider(IDiagnosticAnalyzerService diagnosticService, ImmutableHashSet diagnosticIds) + public FixAllDiagnosticProvider(ImmutableHashSet diagnosticIds) { - _diagnosticService = diagnosticService; - // When computing FixAll for unnecessary pragma suppression diagnostic, // we need to include suppressed diagnostics, as well as reported compiler and analyzer diagnostics. // A null value for '_diagnosticIds' ensures the latter. @@ -46,7 +43,8 @@ private ImmutableArray Filter(ImmutableArray dia public override async Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) { - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForIdsAsync( document.Project, document.Id, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); @@ -55,7 +53,9 @@ public override async Task> GetDocumentDiagnosticsAsync( public override async Task> GetDocumentSpanDiagnosticsAsync(Document document, TextSpan fixAllSpan, CancellationToken cancellationToken) { bool shouldIncludeDiagnostic(string id) => _diagnosticIds == null || _diagnosticIds.Contains(id); - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForSpanAsync( + + var service = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForSpanAsync( document, fixAllSpan, shouldIncludeDiagnostic, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, cancellationToken).ConfigureAwait(false)); @@ -66,7 +66,8 @@ public override async Task> GetDocumentSpanDiagnosticsAs public override async Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) { // Get all diagnostics for the entire project, including document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + var service = project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForIdsAsync( project, documentId: null, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); } @@ -74,7 +75,8 @@ public override async Task> GetAllDiagnosticsAsync(Proje public override async Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) { // Get all no-location diagnostics for the project, doesn't include document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetProjectDiagnosticsForIdsAsync( + var service = project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetProjectDiagnosticsForIdsAsync( project, _diagnosticIds, shouldIncludeAnalyzer: null, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId == null)); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs index b2d43b6471a9b..2383faae92f01 100644 --- a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs @@ -39,7 +39,6 @@ namespace Microsoft.CodeAnalysis.CodeFixes; [Export(typeof(ICodeFixService)), Shared] internal sealed partial class CodeFixService : ICodeFixService { - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableArray> _fixers; private readonly ImmutableDictionary>> _fixersPerLanguageMap; @@ -64,7 +63,6 @@ public CodeFixService( [ImportMany] IEnumerable> fixers, [ImportMany] IEnumerable> configurationProviders) { - _diagnosticService = diagnosticAnalyzerService; _errorLoggers = [.. loggers]; _fixers = [.. fixers]; @@ -105,9 +103,11 @@ public CodeFixService( ImmutableArray allDiagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - allDiagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + allDiagnostics = await service.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), priorityProvider, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); @@ -195,9 +195,11 @@ public async IAsyncEnumerable StreamFixesAsync( // user-invoked diagnostic requests, for example, user invoked Ctrl + Dot operation for lightbulb. ImmutableArray diagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + diagnostics = await service.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), priorityProvider, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); if (!includeSuppressionFixes) @@ -295,9 +297,11 @@ private static SortedDictionary> ConvertToMap( using var _ = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); ImmutableArray diagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + diagnostics = await service.GetDiagnosticsForSpanAsync( document, range, diagnosticId, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, cancellationToken).ConfigureAwait(false); @@ -783,7 +787,7 @@ await diagnosticsWithSameSpan.OrderByDescending(d => d.Severity) var diagnosticProvider = fixAllForInSpan ? new FixAllPredefinedDiagnosticProvider(allDiagnostics) - : (FixAllContext.DiagnosticProvider)new FixAllDiagnosticProvider(_diagnosticService, diagnosticIds); + : (FixAllContext.DiagnosticProvider)new FixAllDiagnosticProvider(diagnosticIds); var codeFixProvider = (fixer as CodeFixProvider) ?? new WrapperCodeFixProvider((IConfigurationFixProvider)fixer, diagnostics.Select(d => d.Id)); From 4f218fde7b77a2993c08aae21f572b116ba16b1b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:23:59 -0700 Subject: [PATCH 097/114] Update tests --- .../CodeCleanup/CSharpCodeCleanupService.cs | 3 ++- .../RenameTracking/RenameTrackingTestState.cs | 1 - .../Test2/Rename/RenameTestHelpers.vb | 1 - .../Features/Cohost/Handlers/Diagnostics.cs | 5 +++-- .../CpsDiagnosticItemSourceTests.vb | 2 +- .../SourceGeneratorItemTests.vb | 1 - .../FSharpDiagnosticAnalyzerService.cs | 19 ++++++------------- 7 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs index 829057ea1964b..09e04eb4ca899 100644 --- a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs +++ b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs @@ -16,7 +16,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeCleanup; [ExportLanguageService(typeof(ICodeCleanupService), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class CSharpCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractCodeCleanupService(codeFixService, diagnosticAnalyzerService) +internal sealed class CSharpCodeCleanupService(ICodeFixService codeFixService) + : AbstractCodeCleanupService(codeFixService) { /// /// Maps format document code cleanup options to DiagnosticId[] diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs index e74ee8de48d20..4c28368add087 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs @@ -98,7 +98,6 @@ public RenameTrackingTestState( var tracker = new RenameTrackingTaggerProvider( Workspace.GetService(), Workspace.GetService(), - Workspace.GetService(), Workspace.GetService(), Workspace.GetService()); diff --git a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb index 19f53caf23725..bf29cf35ecad4 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -109,7 +109,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim tracker = New RenameTrackingTaggerProvider( workspace.GetService(Of IThreadingContext), workspace.GetService(Of IInlineRenameService)(), - workspace.GetService(Of IDiagnosticAnalyzerService)(), workspace.GetService(Of IGlobalOptionService)(), workspace.GetService(Of IAsynchronousOperationListenerProvider)) diff --git a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs index f22cafb4e8384..6e39c496e756b 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs +++ b/src/Tools/ExternalAccess/Razor/Features/Cohost/Handlers/Diagnostics.cs @@ -17,8 +17,9 @@ internal static class Diagnostics { public static async Task> GetDocumentDiagnosticsAsync(Document document, bool supportsVisualStudioExtensions, CancellationToken cancellationToken) { - var globalOptionsService = document.Project.Solution.Services.ExportProvider.GetService(); - var diagnosticAnalyzerService = document.Project.Solution.Services.ExportProvider.GetService(); + var solutionServices = document.Project.Solution.Services; + var globalOptionsService = solutionServices.ExportProvider.GetService(); + var diagnosticAnalyzerService = solutionServices.GetRequiredService(); var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( document, range: null, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb index 1896615e99942..fd1a95b6ad9cb 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb @@ -45,7 +45,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer project.FilePath, project.Id, New MockHierarchyItem() With {.CanonicalName = "\net472\analyzerdependency\" + analyzerPath}, - New FakeAnalyzersCommandHandler, workspace.GetService(Of IDiagnosticAnalyzerService), + New FakeAnalyzersCommandHandler(), listenerProvider) Assert.True(source.HasItems) diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 176b2cc3a8112..c5a06f41e1129 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -272,7 +272,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer workspace.GetService(Of IThreadingContext), New AnalyzerItem(New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, projectId, Nothing, Nothing), analyzerReference, Nothing), New FakeAnalyzersCommandHandler, - workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService), workspace.GetService(Of IAsynchronousOperationListenerProvider)) End Function diff --git a/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs b/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs index 96eac994abd86..6354e43149495 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs @@ -7,24 +7,17 @@ using System; using System.Collections.Generic; using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Diagnostics; -[Shared] -[Export(typeof(IFSharpDiagnosticAnalyzerService))] -internal class FSharpDiagnosticAnalyzerService : IFSharpDiagnosticAnalyzerService +[Export(typeof(IFSharpDiagnosticAnalyzerService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class FSharpDiagnosticAnalyzerService(IDiagnosticsRefresher refresher) : IFSharpDiagnosticAnalyzerService { - private readonly Microsoft.CodeAnalysis.Diagnostics.IDiagnosticAnalyzerService _delegatee; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FSharpDiagnosticAnalyzerService(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticAnalyzerService delegatee) - { - _delegatee = delegatee; - } - public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null, bool highPriority = false) - => _delegatee.RequestDiagnosticRefresh(); + => refresher.RequestWorkspaceRefresh(); } From f76c90e82721cc1cd9ac9ece80538cab487770e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:26:37 -0700 Subject: [PATCH 098/114] Update tests --- .../Test2/CodeFixes/CodeFixServiceTests.vb | 4 +- .../Diagnostics/DiagnosticProviderTests.vb | 2 +- .../Diagnostics/DiagnosticServiceTests.vb | 68 +++++++++---------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index 5176fb2119f8f..65ba884e4422e 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -56,7 +56,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( diagnosticService, @@ -127,7 +127,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( diagnosticService, diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index bbb8f0f4a8012..10b648744e280 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -290,7 +290,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(compilerAnalyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim analyzerService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim analyzerService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Return analyzerService End Function diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 9913c17cfea6b..416331a733318 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -86,7 +86,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim hostAnalyzers = solution.SolutionState.Analyzers Dim project = solution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -192,7 +192,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Add project analyzer reference with no analyzers. Dim projectAnalyzersEmpty = ImmutableArray(Of DiagnosticAnalyzer).Empty @@ -236,7 +236,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference = New AnalyzerImageReference( ImmutableArray.Create(Of DiagnosticAnalyzer)(New TestDiagnosticAnalyzer1(1)), display:=referenceName) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() project = project.WithAnalyzerReferences(ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)) @@ -272,7 +272,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -343,7 +343,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(p2.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim hostAnalyzers = solution.SolutionState.Analyzers Dim workspaceDescriptors = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache) @@ -384,7 +384,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(analyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim diagnosticService2 = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService2 = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptors = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService2.AnalyzerInfoCache) Assert.Equal(1, descriptors.Count) @@ -432,7 +432,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference2 = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location) project = project.AddAnalyzerReference(analyzerReference2) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) ' Verify no duplicate diagnostics. @@ -486,7 +486,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -521,7 +521,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -566,7 +566,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim span = (Await document.GetSyntaxRootAsync().ConfigureAwait(False)).FullSpan Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span).ConfigureAwait(False) @@ -588,7 +588,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() For Each actionKind As OperationAnalyzer.ActionKind In [Enum].GetValues(GetType(OperationAnalyzer.ActionKind)) Dim solution = workspace.CurrentSolution @@ -633,7 +633,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -667,7 +667,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -743,7 +743,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -779,7 +779,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -942,7 +942,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -982,7 +982,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1038,7 +1038,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1090,7 +1090,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1133,7 +1133,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1176,7 +1176,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1227,7 +1227,7 @@ End Class SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1290,7 +1290,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1337,7 +1337,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1408,7 +1408,7 @@ public class B project = additionalDoc.Project Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Await TestCompilationAnalyzerWithAnalyzerOptionsCoreAsync(project, additionalDocText, diagnosticService) @@ -1961,7 +1961,7 @@ End Class SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2021,7 +2021,7 @@ namespace ConsoleApplication1 SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2088,7 +2088,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2127,7 +2127,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2187,7 +2187,7 @@ class C Dim span = localDecl.Span Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify diagnostics for span Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span) @@ -2228,7 +2228,7 @@ class C SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2273,7 +2273,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for the given DiagnosticKind Dim document = project.Documents.Single() @@ -2343,7 +2343,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for fine grained DiagnosticKind in random order Dim document = project.Documents.Single() @@ -2433,7 +2433,7 @@ public class C SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) From 7e847471a544f1ebd1dd8cd77c983092eee4e9c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:27:49 -0700 Subject: [PATCH 099/114] Remove more dependencies --- ...ggerProvider.SingleDiagnosticKindPullTaggerProvider.cs | 8 ++++---- .../InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs index d38f7a89b74eb..e58806e7b93f2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs @@ -35,14 +35,12 @@ internal abstract partial class AbstractDiagnosticsTaggerProvider /// private sealed class SingleDiagnosticKindPullTaggerProvider( AbstractDiagnosticsTaggerProvider callback, - IDiagnosticAnalyzerService analyzerService, DiagnosticKind diagnosticKind, TaggerHost taggerHost, string featureName) : AsynchronousTaggerProvider(taggerHost, featureName) { private readonly DiagnosticKind _diagnosticKind = diagnosticKind; - private readonly IDiagnosticAnalyzerService _analyzerService = analyzerService; // The following three fields are used to help calculate diagnostic performance for syntax errors upon file open. // During TagsChanged notification for syntax errors, VSPlatform will check the buffer's property bag for a @@ -104,7 +102,8 @@ private async Task ProduceTagsAsync( var snapshot = documentSpanToTag.SnapshotSpan.Snapshot; var project = document.Project; - var workspace = project.Solution.Workspace; + var solution = project.Solution; + var workspace = solution.Workspace; // See if we've marked any spans as those we want to suppress diagnostics for. // This can happen for buffers used in the preview workspace where some feature @@ -124,7 +123,8 @@ private async Task ProduceTagsAsync( // NOTE: We pass 'includeSuppressedDiagnostics: true' to ensure that IDE0079 (unnecessary suppressions) // are flagged and faded in the editor. IDE0079 analyzer requires all source suppressed diagnostics to // be provided to it to function correctly. - var diagnostics = await _analyzerService.GetDiagnosticsForSpanAsync( + var analyzerService = solution.Services.GetRequiredService(); + var diagnostics = await analyzerService.GetDiagnosticsForSpanAsync( document, requestedSpan.Span.ToTextSpan(), diagnosticKind: _diagnosticKind, diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs index 257c80587890c..c2b85ea0aa8f3 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs @@ -25,13 +25,11 @@ namespace Microsoft.CodeAnalysis.Editor.InlineDiagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class InlineDiagnosticsTaggerProvider( - IDiagnosticAnalyzerService analyzerService, TaggerHost taggerHost, IEditorFormatMapService editorFormatMapService, IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService) : AbstractDiagnosticsTaggerProvider( - analyzerService, taggerHost, FeatureAttribute.ErrorSquiggles) { From 65e0b6f5958542049138a0890ae65fd5d328523e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:29:14 -0700 Subject: [PATCH 100/114] Update tests --- .../Test/CodeFixes/CodeFixServiceTests.cs | 2 +- .../DiagnosticAnalyzerServiceTests.cs | 22 +++++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 0bb022598afc4..73fbb7acaebb5 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -757,7 +757,7 @@ private static async Task> GetNuGetAndVsixCode using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index cbc369da7c564..cb03323a0cf9f 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -174,8 +174,7 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab var applied = workspace.TryApplyChanges(document.Project.Solution); Assert.True(applied); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); // listen to events var syntaxDiagnostic = false; @@ -214,9 +213,7 @@ private static async Task TestAnalyzerAsync( Func, (bool, bool)> resultSetter, bool expectedSyntax, bool expectedSemantic) { - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var syntax = false; var semantic = false; @@ -259,8 +256,7 @@ public async Task TestHostAnalyzerErrorNotLeaking() loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A {}"), VersionStamp.Create(), filePath: "test.cs")), filePath: "test.cs")])); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); Assert.NotEmpty(diagnostics); @@ -344,8 +340,7 @@ private static AdhocWorkspace CreateWorkspaceWithProjectAndAnalyzer(DiagnosticAn private static async Task TestFullSolutionAnalysisForProjectAsync(AdhocWorkspace workspace, Project project, bool expectAnalyzerExecuted) { - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); @@ -390,8 +385,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var applied = workspace.TryApplyChanges(project.Solution); Assert.True(applied); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); @@ -455,7 +449,7 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS var project = workspace.CurrentSolution.Projects.Single(); var document = project.Documents.Single(); - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var globalOptions = workspace.GetService(); switch (analysisScope) @@ -571,7 +565,7 @@ void M() else Assert.IsType(document); - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var text = await document.GetTextAsync(); @@ -811,7 +805,7 @@ internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis, workspace.OpenDocument(document.Id); } - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); From 2eee0c2c77c782d0ebe14e861d319760f9f75c4c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 13:33:55 -0700 Subject: [PATCH 101/114] remove more --- .../InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs | 3 +-- .../InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs index f9efa1398c4ca..3aaacb2c06c51 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -31,7 +31,6 @@ internal abstract partial class AbstractDiagnosticsTaggerProvider : ITagge private readonly ImmutableArray _diagnosticsTaggerProviders; public AbstractDiagnosticsTaggerProvider( - IDiagnosticAnalyzerService analyzerService, TaggerHost taggerHost, string featureName) { @@ -48,7 +47,7 @@ public AbstractDiagnosticsTaggerProvider( return; SingleDiagnosticKindPullTaggerProvider CreateDiagnosticsTaggerProvider(DiagnosticKind diagnosticKind) - => new(this, analyzerService, diagnosticKind, taggerHost, featureName); + => new(this, diagnosticKind, taggerHost, featureName); } // Functionality for subclasses to control how this diagnostic tagging operates. All the individual diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs index c2b85ea0aa8f3..e5e814ab7e1ab 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs @@ -30,8 +30,7 @@ internal sealed class InlineDiagnosticsTaggerProvider( IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService) : AbstractDiagnosticsTaggerProvider( - taggerHost, - FeatureAttribute.ErrorSquiggles) + taggerHost, FeatureAttribute.ErrorSquiggles) { private readonly IEditorFormatMap _editorFormatMap = editorFormatMapService.GetEditorFormatMap("text"); private readonly IClassificationFormatMapService _classificationFormatMapService = classificationFormatMapService; From ad0f00360169adf243646efd2251cc8dbab2ebcd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 14:00:00 -0700 Subject: [PATCH 102/114] Simplify furhter --- .../Service/DiagnosticAnalyzerService.cs | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs index 2f9a0c90efe3f..8f1f6180f24e8 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs @@ -49,9 +49,8 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer public IAsynchronousOperationListener Listener { get; } private IGlobalOptionService GlobalOptions { get; } - private readonly ConditionalWeakTable _map = new(); - private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; private readonly IDiagnosticsRefresher _diagnosticsRefresher; + private readonly DiagnosticIncrementalAnalyzer _incrementalAnalyzer; public DiagnosticAnalyzerService( IGlobalOptionService globalOptions, @@ -64,7 +63,7 @@ public DiagnosticAnalyzerService( Listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService); GlobalOptions = globalOptions; _diagnosticsRefresher = diagnosticsRefresher; - _createIncrementalAnalyzer = _ => new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); + _incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); globalOptions.AddOptionChangedHandler(this, (_, _, e) => { @@ -100,9 +99,6 @@ public static bool IsGlobalOptionAffectingDiagnostics(IOption2 option) public void RequestDiagnosticRefresh() => _diagnosticsRefresher.RequestWorkspaceRefresh(); - private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - => _map.GetValue(workspace, _createIncrementalAnalyzer); - public async Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, @@ -111,27 +107,21 @@ public async Task> GetDiagnosticsForSpanAsync( DiagnosticKind diagnosticKinds, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(document.Project.Solution.Workspace); - // always make sure that analyzer is called on background thread. await Task.Yield().ConfigureAwait(false); priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); - return await analyzer.GetDiagnosticsForSpanAsync( + return await _incrementalAnalyzer.GetDiagnosticsForSpanAsync( document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, cancellationToken).ConfigureAwait(false); } - public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); - } + public Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + => _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken); public Task> GetDiagnosticsForIdsAsync( Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return _incrementalAnalyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( @@ -139,8 +129,7 @@ public Task> GetProjectDiagnosticsForIdsAsync( Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return analyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); + return _incrementalAnalyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); } public TestAccessor GetTestAccessor() @@ -149,8 +138,6 @@ public TestAccessor GetTestAccessor() public readonly struct TestAccessor(DiagnosticAnalyzerService service) { public Task> GetAnalyzersAsync(Project project, CancellationToken cancellationToken) - { - return service.CreateIncrementalAnalyzer(project.Solution.Workspace).GetAnalyzersForTestingPurposesOnlyAsync(project, cancellationToken); - } + => service._incrementalAnalyzer.GetAnalyzersForTestingPurposesOnlyAsync(project, cancellationToken); } } From bf595908e24d97b88f343ef2dcbbed64ad7cb337 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 14:08:47 -0700 Subject: [PATCH 103/114] Make optional --- .../Diagnostics/Service/DiagnosticAnalyzerService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs index 8f1f6180f24e8..57f6d42564646 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -27,7 +26,7 @@ internal sealed class DiagnosticAnalyzerServiceFactory( IGlobalOptionService globalOptions, IDiagnosticsRefresher diagnosticsRefresher, DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, - IAsynchronousOperationListenerProvider listenerProvider) : IWorkspaceServiceFactory + [Import(AllowDefault = true)] IAsynchronousOperationListenerProvider? listenerProvider) : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { @@ -56,11 +55,11 @@ public DiagnosticAnalyzerService( IGlobalOptionService globalOptions, IDiagnosticsRefresher diagnosticsRefresher, DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, - IAsynchronousOperationListenerProvider listenerProvider, + IAsynchronousOperationListenerProvider? listenerProvider, Workspace workspace) { AnalyzerInfoCache = globalCache.AnalyzerInfoCache; - Listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService); + Listener = listenerProvider?.GetListener(FeatureAttribute.DiagnosticService) ?? AsynchronousOperationListenerProvider.NullListener; GlobalOptions = globalOptions; _diagnosticsRefresher = diagnosticsRefresher; _incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); From 1d505a0595284bbe9494c44e93c2b4c6aa890473 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 14:12:23 -0700 Subject: [PATCH 104/114] Remove dependency --- src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs index 2383faae92f01..985b80e7f7d14 100644 --- a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs @@ -58,7 +58,6 @@ internal sealed partial class CodeFixService : ICodeFixService [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CodeFixService( - IDiagnosticAnalyzerService diagnosticAnalyzerService, [ImportMany] IEnumerable> loggers, [ImportMany] IEnumerable> fixers, [ImportMany] IEnumerable> configurationProviders) From bfa4780bf9b60eced993f601a9ffe0da8b8cdcef Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Mon, 28 Apr 2025 14:31:53 -0700 Subject: [PATCH 105/114] Add dump reporting for https://github.com/dotnet/roslyn/issues/76225 (#78354) * Add dump reporting for https://github.com/dotnet/roslyn/issues/76225 This is very likely somehow related to https://github.com/dotnet/roslyn/pull/74728 but is proving very difficult to track down without a repro or a dump. --- .../Core/Portable/Diagnostics/DiagnosticData.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index 6a94fe489233f..c358e23471480 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -9,6 +9,7 @@ using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -180,7 +181,17 @@ void GetLocationInfo(out FileLinePositionSpan originalLineInfo, out FileLinePosi } else { - originalLineInfo = location.GetLineSpan(); + try + { + originalLineInfo = location.GetLineSpan(); + } + catch (Exception e) when (FatalError.ReportWithDumpAndCatch(e, ErrorSeverity.Critical)) + { + // Help track down https://github.com/dotnet/roslyn/issues/76225 which appears to be an + // issue caused by https://github.com/dotnet/roslyn/pull/74728 + throw; + } + mappedLineInfo = location.GetMappedLineSpan(); } } From c9ef0c78b9a0191b37ea55ca3f3b51b0046e93d8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 14:51:43 -0700 Subject: [PATCH 106/114] Do not offer use-expr-body for properties with initializers --- .../Helpers/UseExpressionBodyHelper`1.cs | 24 ++++++---- ...xpressionBodyForPropertiesAnalyzerTests.cs | 47 ++++++++++++++++++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs index 53127b96f51f7..64f153aff73a1 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs @@ -153,20 +153,24 @@ protected bool TryConvertToExpressionBodyForBaseProperty( [NotNullWhen(true)] out ArrowExpressionClauseSyntax? arrowExpression, out SyntaxToken semicolonToken) { - if (TryConvertToExpressionBodyWorker(declaration, conversionPreference, cancellationToken, out arrowExpression, out semicolonToken)) + // If we have `X Prop { ... } = ...;` we can't convert this as expr-bodied properties can't have initializers. + if (declaration is PropertyDeclarationSyntax { Initializer: null }) { - return true; - } + if (TryConvertToExpressionBodyWorker(declaration, conversionPreference, cancellationToken, out arrowExpression, out semicolonToken)) + return true; - var getAccessor = GetSingleGetAccessor(declaration.AccessorList); - if (getAccessor?.ExpressionBody != null && - BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) - { - arrowExpression = ArrowExpressionClause(getAccessor.ExpressionBody.Expression); - semicolonToken = getAccessor.SemicolonToken; - return true; + var getAccessor = GetSingleGetAccessor(declaration.AccessorList); + if (getAccessor?.ExpressionBody != null && + BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) + { + arrowExpression = ArrowExpressionClause(getAccessor.ExpressionBody.Expression); + semicolonToken = getAccessor.SemicolonToken; + return true; + } } + arrowExpression = null; + semicolonToken = default; return false; } diff --git a/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForPropertiesAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForPropertiesAnalyzerTests.cs index 6eb1e14d2a108..74b213cd90b3f 100644 --- a/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForPropertiesAnalyzerTests.cs +++ b/src/Analyzers/CSharp/Tests/UseExpressionBody/UseExpressionBodyForPropertiesAnalyzerTests.cs @@ -2,10 +2,12 @@ // 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.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.UseExpressionBody; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; @@ -22,7 +24,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody; [Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)] public sealed class UseExpressionBodyForPropertiesAnalyzerTests { - private static async Task TestWithUseExpressionBody(string code, string fixedCode, LanguageVersion version = LanguageVersion.CSharp8) + private static async Task TestWithUseExpressionBody( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedCode, + LanguageVersion version = LanguageVersion.CSharp8) { await new VerifyCS.Test { @@ -38,7 +43,9 @@ private static async Task TestWithUseExpressionBody(string code, string fixedCod }.RunAsync(); } - private static async Task TestWithUseBlockBody(string code, string fixedCode) + private static async Task TestWithUseBlockBody( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fixedCode) { await new VerifyCS.Test { @@ -580,4 +587,40 @@ public long Length //N """; await TestWithUseExpressionBody(code, fixedCode); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77473")] + public async Task TestMissingWithInitializer1() + { + var code = """ + class C + { + object Goo + { + get + { + return field; + } + } = new(); + } + """; + await TestWithUseExpressionBody(code, code, LanguageVersionExtensions.CSharpNext); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77473")] + public async Task TestMissingWithInitializer2() + { + var code = """ + class C + { + object Goo + { + get + { + return field ??= new(); + } + } = new(); + } + """; + await TestWithUseExpressionBody(code, code, LanguageVersionExtensions.CSharpNext); + } } From e70ac77267d9943190f674fa37639eb574dd5a90 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 14:53:04 -0700 Subject: [PATCH 107/114] Fix tests --- .../Test/CodeFixes/CodeFixServiceTests.cs | 12 +++--------- .../Test2/CodeFixes/CodeFixServiceTests.vb | 2 -- .../Diagnostics/Suppression/SuppressionTests.cs | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 73fbb7acaebb5..2b49246cb1e37 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -50,7 +50,7 @@ public async Task TestGetFirstDiagnosticWithFixAsync() var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( - diagnosticService, logger, fixers, configurationProviders: []); + logger, fixers, configurationProviders: []); var reference = new MockAnalyzerReference(); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); @@ -363,12 +363,7 @@ private static (EditorTestWorkspace workspace, CodeFixService codeFixService, IE ? workspace.ExportProvider.GetExports() : []; - var fixService = new CodeFixService( - diagnosticService, - logger, - fixers, - configurationFixProviders); - + var fixService = new CodeFixService(logger, fixers, configurationFixProviders); return (workspace, fixService, errorLogger); } @@ -760,8 +755,7 @@ private static async Task> GetNuGetAndVsixCode var diagnosticService = workspace.Services.GetRequiredService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); - var fixService = new CodeFixService( - diagnosticService, logger, vsixFixers, configurationProviders: []); + var fixService = new CodeFixService(logger, vsixFixers, configurationProviders: []); diagnosticAnalyzer ??= new MockAnalyzerReference.MockDiagnosticAnalyzer(); var analyzers = ImmutableArray.Create(diagnosticAnalyzer); diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index 65ba884e4422e..da7fef606b2a5 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -59,7 +59,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( - diagnosticService, logger, {New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)( Function() workspaceCodeFixProvider, @@ -130,7 +129,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( - diagnosticService, logger, {New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)( Function() workspaceCodeFixProvider, diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index a678cd2e01cc5..a9493c70def88 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -451,7 +451,6 @@ void Method() var suppressionProviderFactory = new Lazy(() => suppressionProvider, new CodeChangeProviderMetadata("SuppressionProvider", languages: [LanguageNames.CSharp])); var fixService = new CodeFixService( - diagnosticService, loggers: [], fixers: [], [suppressionProviderFactory]); From 02a8b351452f666291280176207e83d2150c5834 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 28 Apr 2025 15:36:57 -0700 Subject: [PATCH 108/114] Add BuildHost APIs for loading an in-memory project (#78303) --- .../BuildHost/Build/ProjectBuildManager.cs | 41 +++++++++++++++++++ src/Workspaces/MSBuild/BuildHost/BuildHost.cs | 29 +++++++++++++ .../MSBuild/ProjectFile/ProjectFileLoader.cs | 21 ++++++++++ .../BuildHost/Rpc/Contracts/IBuildHost.cs | 6 +++ .../MSBuild/Core/Rpc/RemoteBuildHost.cs | 10 +++++ src/Workspaces/MSBuild/Test/NetCoreTests.cs | 23 +++++++++++ 6 files changed, 130 insertions(+) diff --git a/src/Workspaces/MSBuild/BuildHost/Build/ProjectBuildManager.cs b/src/Workspaces/MSBuild/BuildHost/Build/ProjectBuildManager.cs index 44b75e3ed52b8..7d0383d959128 100644 --- a/src/Workspaces/MSBuild/BuildHost/Build/ProjectBuildManager.cs +++ b/src/Workspaces/MSBuild/BuildHost/Build/ProjectBuildManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -95,6 +96,20 @@ private ImmutableDictionary AllGlobalProperties using var stream = FileUtilities.OpenAsyncRead(path); using var readStream = await SerializableBytes.CreateReadableStreamAsync(stream, cancellationToken).ConfigureAwait(false); + return LoadProjectCore(path, readStream, projectCollection, log); + } + catch (Exception e) + { + log.Add(e, path); + return (project: null, log); + } + } + + private static (MSB.Evaluation.Project? project, DiagnosticLog log) LoadProjectCore( + string path, Stream readStream, MSB.Evaluation.ProjectCollection? projectCollection, DiagnosticLog log) + { + try + { using var xmlReader = XmlReader.Create(readStream, s_xmlReaderSettings); var xml = MSB.Construction.ProjectRootElement.Create(xmlReader, projectCollection); @@ -151,6 +166,32 @@ private ImmutableDictionary AllGlobalProperties } } + public (MSB.Evaluation.Project? project, DiagnosticLog log) LoadProject(string path, Stream readStream) + { + var log = new DiagnosticLog(); + try + { + var projectCollection = new MSB.Evaluation.ProjectCollection( + AllGlobalProperties, + _msbuildLogger != null ? [_msbuildLogger] : ImmutableArray.Empty, + MSB.Evaluation.ToolsetDefinitionLocations.Default); + try + { + return LoadProjectCore(path, readStream, projectCollection, log); + } + finally + { + // unload project so collection will release global strings + projectCollection.UnloadAllProjects(); + } + } + catch (Exception e) + { + log.Add(e, path); + return (project: null, log); + } + } + public async Task TryGetOutputFilePathAsync( string path, CancellationToken cancellationToken) { diff --git a/src/Workspaces/MSBuild/BuildHost/BuildHost.cs b/src/Workspaces/MSBuild/BuildHost/BuildHost.cs index c74d8ff18494f..86c59f00d115f 100644 --- a/src/Workspaces/MSBuild/BuildHost/BuildHost.cs +++ b/src/Workspaces/MSBuild/BuildHost/BuildHost.cs @@ -167,6 +167,15 @@ public Task LoadProjectFileAsync(string projectFilePath, string languageNam return LoadProjectFileCoreAsync(projectFilePath, languageName, cancellationToken); } + /// + /// Returns the target ID of the object created for this. + /// + public int LoadProject(string projectFilePath, string projectContent, string languageName) + { + EnsureMSBuildLoaded(projectFilePath); + return LoadProjectCore(projectFilePath, projectContent, languageName); + } + // When using the Mono runtime, the MSBuild types used in this method must be available // to the JIT during compilation of the method, so they have to be loaded by the caller; // therefore this method must not be inlined. @@ -187,6 +196,26 @@ private async Task LoadProjectFileCoreAsync(string projectFilePath, string return _server.AddTarget(projectFile); } + // When using the Mono runtime, the MSBuild types used in this method must be available + // to the JIT during compilation of the method, so they have to be loaded by the caller; + // therefore this method must not be inlined. + [MethodImpl(MethodImplOptions.NoInlining)] + private int LoadProjectCore(string projectFilePath, string projectContent, string languageName) + { + CreateBuildManager(); + + ProjectFileLoader projectLoader = languageName switch + { + LanguageNames.CSharp => new CSharpProjectFileLoader(), + LanguageNames.VisualBasic => new VisualBasicProjectFileLoader(), + _ => throw ExceptionUtilities.UnexpectedValue(languageName) + }; + + _logger.LogInformation($"Loading an in-memory project with the path {projectFilePath}"); + var projectFile = projectLoader.LoadProject(projectFilePath, projectContent, _buildManager); + return _server.AddTarget(projectFile); + } + public Task TryGetProjectOutputPathAsync(string projectFilePath, CancellationToken cancellationToken) { EnsureMSBuildLoaded(projectFilePath); diff --git a/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs b/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs index 0691d286ae013..2dc6b260c9224 100644 --- a/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs +++ b/src/Workspaces/MSBuild/BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using MSB = Microsoft.Build; @@ -27,4 +29,23 @@ public async Task LoadProjectFileAsync(string path, ProjectBuildMan return this.CreateProjectFile(project, buildManager, log); } + + public ProjectFile LoadProject(string path, string projectContent, ProjectBuildManager buildManager) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + // We expect MSBuild to consume this stream with a utf-8 encoding. + // This is because we expect the stream we create to not include a BOM nor an an encoding declaration a la ``. + // In this scenario, the XML standard requires XML processors to consume the document with a UTF-8 encoding. + // https://www.w3.org/TR/xml/#d0e4623 + // Theoretically we could also enforce that 'projectContent' does not contain an encoding declaration with non-UTF-8 encoding. + // But it seems like a very unlikely scenario to actually get into--this is not something people generally put on real project files. + var stream = new MemoryStream(Encoding.UTF8.GetBytes(projectContent)); + var (project, log) = buildManager.LoadProject(path, stream); + + return this.CreateProjectFile(project, buildManager, log); + } } diff --git a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs index dcc0e48ef2f5d..4bde82f6a5a19 100644 --- a/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs +++ b/src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs @@ -16,6 +16,12 @@ internal interface IBuildHost bool HasUsableMSBuild(string projectOrSolutionFilePath); ImmutableArray<(string ProjectPath, string ProjectGuid)> GetProjectsInSolution(string solutionFilePath); Task LoadProjectFileAsync(string projectFilePath, string languageName, CancellationToken cancellationToken); + + /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios. + /// A path to a project file which may or may not exist on disk. Note that an extension that is known by MSBuild, such as .csproj or .vbproj, should be used here. + /// The project file XML content. + int LoadProject(string projectFilePath, string projectContent, string languageName); + Task TryGetProjectOutputPathAsync(string projectFilePath, CancellationToken cancellationToken); Task ShutdownAsync(); } diff --git a/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs b/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs index 9af22be350073..eecafbfcbefda 100644 --- a/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs +++ b/src/Workspaces/MSBuild/Core/Rpc/RemoteBuildHost.cs @@ -33,6 +33,16 @@ public async Task LoadProjectFileAsync(string projectFilePath return new RemoteProjectFile(_client, remoteProjectFileTargetObject); } + /// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios. + /// A path to a project file which may or may not exist on disk. Note that an extension that is known by MSBuild, such as .csproj or .vbproj, should be used here. + /// The project file XML content. + public async Task LoadProjectAsync(string projectFilePath, string projectContent, string languageName, CancellationToken cancellationToken) + { + var remoteProjectFileTargetObject = await _client.InvokeAsync(BuildHostTargetObject, nameof(IBuildHost.LoadProject), parameters: [projectFilePath, projectContent, languageName], cancellationToken).ConfigureAwait(false); + + return new RemoteProjectFile(_client, remoteProjectFileTargetObject); + } + public Task TryGetProjectOutputPathAsync(string projectFilePath, CancellationToken cancellationToken) => _client.InvokeNullableAsync(BuildHostTargetObject, nameof(IBuildHost.TryGetProjectOutputPathAsync), parameters: [projectFilePath], cancellationToken); diff --git a/src/Workspaces/MSBuild/Test/NetCoreTests.cs b/src/Workspaces/MSBuild/Test/NetCoreTests.cs index 0841eed8bbf2a..1eb83d8bdcfaa 100644 --- a/src/Workspaces/MSBuild/Test/NetCoreTests.cs +++ b/src/Workspaces/MSBuild/Test/NetCoreTests.cs @@ -6,8 +6,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; @@ -94,6 +96,27 @@ public async Task TestOpenProject_NetCoreApp() Assert.Empty(diagnostics); } + [ConditionalFact(typeof(DotNetSdkMSBuildInstalled))] + [Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] + [Trait(Traits.Feature, Traits.Features.NetCore)] + public async Task TestOpenInMemoryProject_NetCoreApp() + { + CreateFiles(GetNetCoreAppFiles()); + + var projectFilePath = GetSolutionFileName("Project.csproj"); + var content = File.ReadAllText(projectFilePath); + File.Delete(projectFilePath); + var projectDir = Path.GetDirectoryName(projectFilePath); + + await using var buildHostProcessManager = new BuildHostProcessManager(ImmutableDictionary.Empty); + + var buildHost = await buildHostProcessManager.GetBuildHostAsync(BuildHostProcessManager.BuildHostProcessKind.NetCore, CancellationToken.None); + var projectFile = await buildHost.LoadProjectAsync(projectFilePath, content, LanguageNames.CSharp, CancellationToken.None); + var projectFileInfo = (await projectFile.GetProjectFileInfosAsync(CancellationToken.None)).Single(); + + Assert.Equal(Path.Combine(projectDir, "bin", "Debug", "netcoreapp3.1", "Project.dll"), projectFileInfo.OutputFilePath); + } + [ConditionalFact(typeof(DotNetSdkMSBuildInstalled))] [Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] [Trait(Traits.Feature, Traits.Features.NetCore)] From 710e9a8457472eca477d62aa65e661b09ad8bcb9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 28 Apr 2025 16:39:23 -0700 Subject: [PATCH 109/114] Fix --- .../Helpers/UseExpressionBodyHelper`1.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs index 64f153aff73a1..3cbdab40ca583 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/Helpers/UseExpressionBodyHelper`1.cs @@ -153,24 +153,25 @@ protected bool TryConvertToExpressionBodyForBaseProperty( [NotNullWhen(true)] out ArrowExpressionClauseSyntax? arrowExpression, out SyntaxToken semicolonToken) { + arrowExpression = null; + semicolonToken = default; + // If we have `X Prop { ... } = ...;` we can't convert this as expr-bodied properties can't have initializers. - if (declaration is PropertyDeclarationSyntax { Initializer: null }) - { - if (TryConvertToExpressionBodyWorker(declaration, conversionPreference, cancellationToken, out arrowExpression, out semicolonToken)) - return true; + if (declaration is PropertyDeclarationSyntax { Initializer: not null }) + return false; - var getAccessor = GetSingleGetAccessor(declaration.AccessorList); - if (getAccessor?.ExpressionBody != null && - BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) - { - arrowExpression = ArrowExpressionClause(getAccessor.ExpressionBody.Expression); - semicolonToken = getAccessor.SemicolonToken; - return true; - } + if (TryConvertToExpressionBodyWorker(declaration, conversionPreference, cancellationToken, out arrowExpression, out semicolonToken)) + return true; + + var getAccessor = GetSingleGetAccessor(declaration.AccessorList); + if (getAccessor?.ExpressionBody != null && + BlockSyntaxExtensions.MatchesPreference(getAccessor.ExpressionBody.Expression, conversionPreference)) + { + arrowExpression = ArrowExpressionClause(getAccessor.ExpressionBody.Expression); + semicolonToken = getAccessor.SemicolonToken; + return true; } - arrowExpression = null; - semicolonToken = default; return false; } From 9168a126a25e03e83f510ed34fb66bdb06024518 Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Mon, 28 Apr 2025 19:14:53 -0700 Subject: [PATCH 110/114] minor cleanup (no functional change) --- .../SemanticSeatchTextBufferSupportsFeatureService.cs | 2 +- .../Portable/SemanticSearch/IRemoteSemanticSearchService.cs | 1 - .../Core/Portable/SemanticSearch/ISemanticSearchService.cs | 2 +- .../Portable/SemanticSearch/Tools/ReferencingSyntaxFinder.cs | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs b/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs index 8d5839fc5e475..bd203c03b60cd 100644 --- a/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs +++ b/src/EditorFeatures/Core/SemanticSearch/SemanticSeatchTextBufferSupportsFeatureService.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.SemanticSearch; [ExportWorkspaceService(typeof(ITextBufferSupportsFeatureService), WorkspaceKind.SemanticSearch), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class SemanticSeatchTextBufferSupportsFeatureService() : ITextBufferSupportsFeatureService +internal sealed class SemanticSearchTextBufferSupportsFeatureService() : ITextBufferSupportsFeatureService { public bool SupportsCodeFixes(ITextBuffer textBuffer) => true; diff --git a/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs b/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs index 107beb02f6098..d21865d836915 100644 --- a/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs +++ b/src/Features/Core/Portable/SemanticSearch/IRemoteSemanticSearchService.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; diff --git a/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs index 3b80cf8d066b5..587dc2a465b80 100644 --- a/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs +++ b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchService.cs @@ -16,7 +16,7 @@ internal interface ISemanticSearchService : ILanguageService /// Compiles a query. The query has to be executed or discarded. /// /// Query (top-level code). - /// Directory that contains refernece assemblies to be used for compilation of the query. + /// Directory that contains reference assemblies to be used for compilation of the query. CompileQueryResult CompileQuery( SolutionServices services, string query, diff --git a/src/Features/Core/Portable/SemanticSearch/Tools/ReferencingSyntaxFinder.cs b/src/Features/Core/Portable/SemanticSearch/Tools/ReferencingSyntaxFinder.cs index ddbdaddd77e11..ff43172a6a095 100644 --- a/src/Features/Core/Portable/SemanticSearch/Tools/ReferencingSyntaxFinder.cs +++ b/src/Features/Core/Portable/SemanticSearch/Tools/ReferencingSyntaxFinder.cs @@ -34,7 +34,7 @@ public async IAsyncEnumerable FindAsync(ISymbol symbol) { using var _ = PooledHashSet.GetInstance(out var cachedRoots); - // Kick off the SymbolFinder.FindReferencesAsync call on the provided symbol/solution. As it finds + // Kick off the SymbolFinder.FindReferencesAsync call on the provided symbol/solution. As it finds // ReferenceLocations, it will push those into the 'callback' delegate passed into it. ProducerConsumer will // then convert this to a simple IAsyncEnumerable that we can iterate over, converting those // locations to SyntaxNodes in the corresponding C# or VB document. From 9233fa321a44cb80441a99954ad7cbc3408cc40f Mon Sep 17 00:00:00 2001 From: Alex Wiese Date: Tue, 29 Apr 2025 16:47:02 +0930 Subject: [PATCH 111/114] Insert missing space in XML documentation (#78361) --- .../Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.cs index df031a9baaee5..741184d63e8cc 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.cs @@ -37,7 +37,7 @@ internal SyntaxValueProvider( /// /// The type of the value the syntax node is transformed into /// A function that determines if the given should be transformed - /// A function that performs the transform, when returns true for a given node + /// A function that performs the transform, when returns true for a given node /// An that provides the results of the transformation public IncrementalValuesProvider CreateSyntaxProvider(Func predicate, Func transform) { From 748b0e60d1ea4f5d7f294024338c1649f58330ad Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Tue, 29 Apr 2025 15:28:22 +0200 Subject: [PATCH 112/114] Fix async hoisted type substituted local (#78231) fixes #76999 Support TypeSubstitutedLocalSymbol's in MethodToStateMachineRewriter.HoistRefInitialization --- ...TreeToDifferentEnclosingContextRewriter.cs | 46 +++++++ .../Lowering/MethodToClassRewriter.cs | 28 ----- .../MethodToStateMachineRewriter.cs | 13 +- .../Synthesized/TypeSubstitutedLocalSymbol.cs | 12 +- .../Semantics/AwaitExpressionTests.cs | 116 ++++++++++++++++++ 5 files changed, 183 insertions(+), 32 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs index f4874b9a49159..2496aa0422e52 100644 --- a/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs @@ -23,6 +23,9 @@ internal abstract class BoundTreeToDifferentEnclosingContextRewriter : BoundTree // though its containing method is not correct because the code is moved into another method) private readonly Dictionary localMap = new Dictionary(); + //to handle type changes (e.g. type parameters) we need to update placeholders + private readonly Dictionary _placeholderMap = new Dictionary(); + // A mapping for types in the original method to types in its replacement. This is mainly necessary // when the original method was generic, as type parameters in the original method are mapping into // type parameters of the resulting class. @@ -114,6 +117,31 @@ protected BoundBlock VisitBlock(BoundBlock node, bool removeInstrumentation) return TypeMap.SubstituteType(type).Type; } + public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node) + { + var awaitablePlaceholder = node.AwaitableInstancePlaceholder; + if (awaitablePlaceholder is null) + { + return node; + } + + var rewrittenPlaceholder = awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type)); + _placeholderMap.Add(awaitablePlaceholder, rewrittenPlaceholder); + + var getAwaiter = (BoundExpression?)this.Visit(node.GetAwaiter); + var isCompleted = VisitPropertySymbol(node.IsCompleted); + var getResult = VisitMethodSymbol(node.GetResult); + + _placeholderMap.Remove(awaitablePlaceholder); + + return node.Update(rewrittenPlaceholder, node.IsDynamic, getAwaiter, isCompleted, getResult); + } + + public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node) + { + return _placeholderMap[node]; + } + protected override BoundBinaryOperator.UncommonData? VisitBinaryOperatorData(BoundBinaryOperator node) { // Local rewriter should have already rewritten interpolated strings into their final form of calls and gotos @@ -142,6 +170,24 @@ protected BoundBlock VisitBlock(BoundBlock node, bool removeInstrumentation) VisitType(node.Type)); } + [return: NotNullIfNotNull(nameof(property))] + public override PropertySymbol? VisitPropertySymbol(PropertySymbol? property) + { + if (property is null) + { + return null; + } + if (property.ContainingType.IsAnonymousType) + { + //at this point we expect that the code is lowered and that getters of anonymous types are accessed + //only via their corresponding get-methods (see VisitMethodSymbol) + throw ExceptionUtilities.Unreachable(); + } + return ((PropertySymbol)property.OriginalDefinition) + .AsMember((NamedTypeSymbol)TypeMap.SubstituteType(property.ContainingType).AsTypeSymbolOnly()) + ; + } + [return: NotNullIfNotNull(nameof(method))] public override MethodSymbol? VisitMethodSymbol(MethodSymbol? method) { diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index a870c8678eb7f..85e48d715dd47 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -35,8 +35,6 @@ internal abstract partial class MethodToClassRewriter : BoundTreeToDifferentEncl protected readonly BindingDiagnosticBag Diagnostics; protected readonly VariableSlotAllocator? slotAllocator; - private readonly Dictionary _placeholderMap; - protected MethodToClassRewriter(VariableSlotAllocator? slotAllocator, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { Debug.Assert(compilationState != null); @@ -46,7 +44,6 @@ protected MethodToClassRewriter(VariableSlotAllocator? slotAllocator, TypeCompil this.CompilationState = compilationState; this.Diagnostics = diagnostics; this.slotAllocator = slotAllocator; - this._placeholderMap = new Dictionary(); } /// @@ -254,31 +251,6 @@ private bool TryGetHoistedField(Symbol variable, [NotNullWhen(true)] out FieldSy return false; } - public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node) - { - var awaitablePlaceholder = node.AwaitableInstancePlaceholder; - if (awaitablePlaceholder is null) - { - return node; - } - - var rewrittenPlaceholder = awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type)); - _placeholderMap.Add(awaitablePlaceholder, rewrittenPlaceholder); - - var getAwaiter = (BoundExpression?)this.Visit(node.GetAwaiter); - var isCompleted = VisitPropertySymbol(node.IsCompleted); - var getResult = VisitMethodSymbol(node.GetResult); - - _placeholderMap.Remove(awaitablePlaceholder); - - return node.Update(rewrittenPlaceholder, node.IsDynamic, getAwaiter, isCompleted, getResult); - } - - public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node) - { - return _placeholderMap[node]; - } - public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundExpression originalLeft = node.Left; diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 4221e9de09565..3671e459416df 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -505,11 +505,18 @@ private void FreeReusableHoistedField(StateMachineFieldSymbol field) fields.Add(field); } - private BoundExpression HoistRefInitialization(SynthesizedLocal local, BoundAssignmentOperator node) + private BoundExpression HoistRefInitialization(LocalSymbol local, BoundAssignmentOperator node) { + Debug.Assert( + local switch + { + TypeSubstitutedLocalSymbol tsl => tsl.UnderlyingLocalSymbol, + _ => local + } is SynthesizedLocal + ); Debug.Assert(local.SynthesizedKind == SynthesizedLocalKind.Spill || (local.SynthesizedKind == SynthesizedLocalKind.ForEachArray && local.Type.HasInlineArrayAttribute(out _) && local.Type.TryGetInlineArrayElementField() is object)); - Debug.Assert(local.SyntaxOpt != null); + Debug.Assert(local.GetDeclaratorSyntax() != null); #pragma warning disable format Debug.Assert(local.SynthesizedKind switch { @@ -856,7 +863,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) // We have an assignment to a variable that has not yet been assigned a proxy. // So we assign the proxy before translating the assignment. - return HoistRefInitialization((SynthesizedLocal)leftLocal, node); + return HoistRefInitialization(leftLocal, node); } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs index 7de14256bbd2c..0e5cceb260337 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs @@ -24,7 +24,15 @@ public TypeSubstitutedLocalSymbol(LocalSymbol originalVariable, TypeWithAnnotati Debug.Assert(containingSymbol != null); Debug.Assert(containingSymbol.DeclaringCompilation is not null); - _originalVariable = originalVariable; + //avoid double wrapping + //this can happen e.g. if a local is substituted in ExtensionMethodBodyRewriter + //and later it is substituted for example in ClosureConversion again + _originalVariable = originalVariable switch + { + TypeSubstitutedLocalSymbol tsl => tsl._originalVariable, + var l => l + }; + _type = type; _containingSymbol = containingSymbol; } @@ -123,6 +131,8 @@ internal override ReadOnlyBindingDiagnostic GetConstantValueDiag return _originalVariable.GetConstantValueDiagnostics(boundInitValue); } + internal LocalSymbol UnderlyingLocalSymbol => _originalVariable; + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax( SynthesizedLocalKind kind, SyntaxNode syntax #if DEBUG diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/AwaitExpressionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/AwaitExpressionTests.cs index c47850beca419..1ff6927f6275c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/AwaitExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/AwaitExpressionTests.cs @@ -119,6 +119,122 @@ async void Goo(Task t) Assert.Equal("System.Boolean System.Runtime.CompilerServices.TaskAwaiter.IsCompleted { get; }", info.IsCompletedProperty.ToTestDisplayString()); } + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/76999")] + public void TestAwaitHoistedRef() + { + var src = """ + using System.Threading.Tasks; + + public sealed class RefHolder + { + private T _t; + public ref T Get() => ref _t; + } + + public static class App + { + public static void Do() + { + var res = new RefHolder(); + M().Wait(); + async Task M() + { + res.Get() = await Task.FromResult(default(T)); + } + } + } + """; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (17,13): error CS8178: A reference returned by a call to 'RefHolder.Get()' cannot be preserved across 'await' or 'yield' boundary. + // res.Get() = await Task.FromResult(default(T)); + Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res.Get()").WithArguments("RefHolder.Get()").WithLocation(17, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/76999")] + public void TestAwaitHoistedRef_InNewExtensionContainer() + { + var src = """ + using System.Threading.Tasks; + + public sealed class RefHolder + { + private T _t; + public ref T Get() => ref _t; + } + + public static class App + { + extension(int) + { + public static void Do() + { + var res = new RefHolder(); + M().Wait(); + async Task M() + { + res.Get() = await Task.FromResult(default(T)); + } + } + } + } + """; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (19,17): error CS8178: A reference returned by a call to 'RefHolder.Get()' cannot be preserved across 'await' or 'yield' boundary. + // res.Get() = await Task.FromResult(default(T)); + Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res.Get()").WithArguments("RefHolder.Get()").WithLocation(19, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/76999")] + public void TestAwaitHoistedRef2() + { + var src = """ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public sealed class ValuesHolder + { + private readonly T[] _values = new T[10]; + + public ref T this[int type] => ref _values[type]; + } + + public static class App + { + public static async Task> Do() + { + var res = new ValuesHolder(); + + var taskGroup = new List>>(); + + await Task.WhenAll(taskGroup.Select(async kv => + { + res[0] = await kv.Value; + })); + + return res; + } + } + """; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (23,4): error CS8178: A reference returned by a call to 'ValuesHolder.this[int].get' cannot be preserved across 'await' or 'yield' boundary. + // res[0] = await kv.Value; + Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res[0]").WithArguments("ValuesHolder.this[int].get").WithLocation(23, 4) + ); + } + [Fact] [WorkItem(1084696, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1084696")] public void TestAwaitInfo2() From eb89850dedc48eee4650093d80fced852032da49 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 29 Apr 2025 08:08:41 -0700 Subject: [PATCH 113/114] Move remaining legacy workspace event listeners over to new APIs. (#78333) About 4.1% of main thread CPU costs during solution load are in workspace eventing. This moves nearly all those costs to background threads. The majority of the workspace changed handler costs are associated with ProjectCodeModelFactoy. If there were any questions about the event handler needing the main thread, this PR attempts to be safe, and continue to use the main thread. Future PRs can judiciously look at moving event handlers to work on background threads. Test insertion PR: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/631924 See PR for before/after images of CPU usage characteristics. --- .../PdbMatchingSourceTextProvider.cs | 15 ++++++++++++--- .../Aggregator/SettingsAggregator.cs | 4 ++-- .../Core/InlineRename/InlineRenameSession.cs | 11 ++++++++--- ...rEventSources.ParseOptionChangedEventSource.cs | 15 ++++++++++++--- ...gerEventSources.WorkspaceChangedEventSource.cs | 9 ++++++--- ...tLegacySolutionEventsWorkspaceEventListener.cs | 11 ++++++++--- .../CodeAnalysisDiagnosticAnalyzerService.cs | 8 ++++---- .../Workspace/CompileTimeSolutionProvider.cs | 4 ++-- .../VisualStudioDesignerAttributeService.cs | 9 ++++++--- .../AbstractObjectBrowserLibraryManager.cs | 10 +++++++--- .../Packaging/PackageInstallerServiceFactory.cs | 4 ++-- .../Core/Def/Progression/GraphQueryManager.cs | 2 +- .../StackTraceExplorerViewModel.cs | 8 +++++--- .../Def/ValueTracking/ValueTrackingToolWindow.cs | 8 ++++---- .../Def/Workspace/SourceGeneratedFileManager.cs | 8 +++++--- .../Impl/CodeModel/ProjectCodeModelFactory.cs | 4 ++-- .../AnalyzerItem/AnalyzerItemSource.cs | 9 ++++++--- .../BaseDiagnosticAndGeneratorItemSource.cs | 9 ++++++--- .../DiagnosticItem/CpsDiagnosticItemSource.cs | 12 +++++++++--- .../SourceGeneratedFileItemSource.cs | 13 +++++++------ ...erviceFactory.SemanticModelWorkspaceService.cs | 4 ++-- .../Core/Portable/Workspace/Solution/Project.cs | 3 ++- .../Core/Portable/Workspace/Solution/Solution.cs | 2 ++ 23 files changed, 120 insertions(+), 62 deletions(-) diff --git a/src/EditorFeatures/Core/EditAndContinue/PdbMatchingSourceTextProvider.cs b/src/EditorFeatures/Core/EditAndContinue/PdbMatchingSourceTextProvider.cs index d67a5cfdf7dea..1ddb7fabcc9ee 100644 --- a/src/EditorFeatures/Core/EditAndContinue/PdbMatchingSourceTextProvider.cs +++ b/src/EditorFeatures/Core/EditAndContinue/PdbMatchingSourceTextProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -30,14 +31,22 @@ internal sealed class PdbMatchingSourceTextProvider() : IEventListener, IPdbMatc private bool _isActive; private int _baselineSolutionVersion; private readonly Dictionary _documentsWithChangedLoaderByPath = []; + private WorkspaceEventRegistration? _workspaceChangedDisposer; public void StartListening(Workspace workspace) - => workspace.WorkspaceChanged += WorkspaceChanged; + { + Debug.Assert(_workspaceChangedDisposer == null); + + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(WorkspaceChanged); + } public void StopListening(Workspace workspace) - => workspace.WorkspaceChanged -= WorkspaceChanged; + { + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + } - private void WorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void WorkspaceChanged(WorkspaceChangeEventArgs e) { if (!_isActive) { diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index b5050c4bc7d3c..a3e0f455df219 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -32,7 +32,7 @@ public SettingsAggregator( IAsynchronousOperationListener listener) { _workspace = workspace; - _workspace.WorkspaceChanged += UpdateProviders; + _ = workspace.RegisterWorkspaceChangedHandler(UpdateProviders); var currentSolution = _workspace.CurrentSolution.SolutionState; UpdateProviders(currentSolution); @@ -48,7 +48,7 @@ public SettingsAggregator( threadingContext.DisposalToken); } - private void UpdateProviders(object? sender, WorkspaceChangeEventArgs e) + private void UpdateProviders(WorkspaceChangeEventArgs e) { switch (e.Kind) { diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index a43f23d3a545a..8c1cdffb5808a 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -51,6 +51,8 @@ internal sealed partial class InlineRenameSession : IInlineRenameSession, IFeatu private readonly ITextView _triggerView; private readonly IDisposable _inlineRenameSessionDurationLogBlock; private readonly IThreadingContext _threadingContext; + private readonly WorkspaceEventRegistration _workspaceChangedDisposer; + public readonly InlineRenameService RenameService; private bool _dismissed; @@ -161,7 +163,9 @@ public InlineRenameSession( _inlineRenameSessionDurationLogBlock = Logger.LogBlock(FunctionId.Rename_InlineSession, CancellationToken.None); Workspace = workspace; - Workspace.WorkspaceChanged += OnWorkspaceChanged; + + // Requires the main thread due to OnWorkspaceChanged calling the Cancel method + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged, WorkspaceEventOptions.RequiresMainThreadOptions); _textBufferFactoryService = textBufferFactoryService; _textBufferCloneService = textBufferCloneService; @@ -383,7 +387,7 @@ private void VerifyNotDismissed() } } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs args) { if (args.Kind != WorkspaceChangeKind.DocumentChanged) { @@ -701,7 +705,8 @@ private void DismissUIAndRollbackEditsAndEndRenameSession_MustBeCalledOnUIThread void DismissUIAndRollbackEdits() { - Workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer.Dispose(); + _textBufferAssociatedViewService.SubjectBuffersConnected -= OnSubjectBuffersConnected; // Reenable completion now that the inline rename session is done diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs index 447f5e99f7872..074daf5c33638 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ParseOptionChangedEventSource.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.Diagnostics; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; @@ -13,13 +14,21 @@ internal partial class TaggerEventSources { private sealed class ParseOptionChangedEventSource(ITextBuffer subjectBuffer) : AbstractWorkspaceTrackingTaggerEventSource(subjectBuffer) { + private WorkspaceEventRegistration? _workspaceChangedDisposer; + protected override void ConnectToWorkspace(Workspace workspace) - => workspace.WorkspaceChanged += OnWorkspaceChanged; + { + Debug.Assert(_workspaceChangedDisposer == null); + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); + } protected override void DisconnectFromWorkspace(Workspace workspace) - => workspace.WorkspaceChanged -= OnWorkspaceChanged; + { + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { if (e.Kind == WorkspaceChangeKind.ProjectChanged) { diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs index 920db2ca74baf..51a1735aab535 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.WorkspaceChangedEventSource.cs @@ -15,6 +15,7 @@ internal partial class TaggerEventSources private sealed class WorkspaceChangedEventSource : AbstractWorkspaceTrackingTaggerEventSource { private readonly AsyncBatchingWorkQueue _asyncDelay; + private WorkspaceEventRegistration? _workspaceChangedDisposer; public WorkspaceChangedEventSource( ITextBuffer subjectBuffer, @@ -36,17 +37,19 @@ public WorkspaceChangedEventSource( protected override void ConnectToWorkspace(Workspace workspace) { - workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); this.RaiseChanged(); } protected override void DisconnectFromWorkspace(Workspace workspace) { - workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + this.RaiseChanged(); } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs eventArgs) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs eventArgs) => _asyncDelay.AddWork(); } } diff --git a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs index 5fe501dff9197..2118302a39f9b 100644 --- a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs +++ b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs @@ -32,6 +32,8 @@ internal sealed partial class HostLegacySolutionEventsWorkspaceEventListener : I private readonly IThreadingContext _threadingContext; private readonly AsyncBatchingWorkQueue _eventQueue; + private WorkspaceEventRegistration? _workspaceChangedDisposer; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public HostLegacySolutionEventsWorkspaceEventListener( @@ -53,16 +55,19 @@ public void StartListening(Workspace workspace) // We only support this option to disable crawling in internal speedometer and ddrit perf runs to lower noise. // It is not exposed to the user. if (_globalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) - workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); } public void StopListening(Workspace workspace) { if (_globalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) - workspace.WorkspaceChanged -= OnWorkspaceChanged; + { + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + } } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { // Legacy workspace events exist solely to let unit testing continue to work using their own fork of solution // crawler. As such, they only need events for the project types they care about. Specifically, that is only diff --git a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs index 53c1f6c66f614..f8d4fc175748f 100644 --- a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; @@ -52,10 +50,12 @@ public CodeAnalysisDiagnosticAnalyzerService( _workspace = workspace; _diagnosticAnalyzerService = _workspace.Services.GetRequiredService(); - _workspace.WorkspaceChanged += OnWorkspaceChanged; + // Main thread as OnWorkspaceChanged's call to IDiagnosticAnalyzerService.RequestDiagnosticRefresh isn't clear on + // threading requirements + _ = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged, WorkspaceEventOptions.RequiresMainThreadOptions); } - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { switch (e.Kind) { diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 2ef0e6ba59d47..f665a3de9e192 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -65,7 +65,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) public CompileTimeSolutionProvider(Workspace workspace) { - workspace.WorkspaceChanged += (s, e) => + _ = workspace.RegisterWorkspaceChangedHandler((e) => { if (e.Kind is WorkspaceChangeKind.SolutionCleared or WorkspaceChangeKind.SolutionRemoved) { @@ -79,7 +79,7 @@ public CompileTimeSolutionProvider(Workspace workspace) _lastCompileTimeSolution = null; } } - }; + }); } private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index 699bfc38552a4..909e1e424d18c 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -60,6 +60,8 @@ internal sealed class VisualStudioDesignerAttributeService : // deliver them to VS in batches to prevent flooding the UI thread. private readonly AsyncBatchingWorkQueue _projectSystemNotificationQueue; + private WorkspaceEventRegistration? _workspaceChangedDisposer; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioDesignerAttributeService( @@ -92,7 +94,7 @@ void IEventListener.StartListening(Workspace workspace) if (workspace != _workspace) return; - _workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); _workQueue.AddWork(cancelExistingWork: true); } @@ -101,10 +103,11 @@ void IEventListener.StopListening(Workspace workspace) if (workspace != _workspace) return; - _workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { _workQueue.AddWork(cancelExistingWork: true); } diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index aba4e28fe4c90..5d543edc697b4 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -41,6 +41,7 @@ internal abstract partial class AbstractObjectBrowserLibraryManager : AbstractLi private ObjectListItem _activeListItem; private AbstractListItemFactory _listItemFactory; private readonly object _classMemberGate = new(); + private WorkspaceEventRegistration _workspaceChangedDisposer; protected AbstractObjectBrowserLibraryManager( string languageName, @@ -53,7 +54,7 @@ protected AbstractObjectBrowserLibraryManager( _languageName = languageName; Workspace = workspace; - Workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); _libraryService = new Lazy(() => Workspace.Services.GetLanguageServices(_languageName).GetService()); } @@ -73,9 +74,12 @@ private AbstractListItemFactory GetListItemFactory() } public void Dispose() - => this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; + { + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { switch (e.Kind) { diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 540578ef2107c..fafe720d01eba 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -232,7 +232,7 @@ protected override async Task EnableServiceAsync(CancellationToken cancellationT var packageSourceProvider = await GetPackageSourceProviderAsync().ConfigureAwait(false); // Start listening to additional events workspace changes. - Workspace.WorkspaceChanged += OnWorkspaceChanged; + _ = Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); packageSourceProvider.SourcesChanged += OnSourceProviderSourcesChanged; // Kick off an initial set of work that will analyze the entire solution. @@ -421,7 +421,7 @@ await UpdateStatusBarAsync( } } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { // ThisCanBeCalledOnAnyThread(); diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs b/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs index 71b051a6fbdda..e5c81b32345f2 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueryManager.cs @@ -50,7 +50,7 @@ internal GraphQueryManager( // indicating a change happened. And when UpdateExistingQueriesAsync fires, it will just see that there are // no live queries and immediately return. So it's just simple to do things this way instead of trying to // have state management where we try to decide if we should listen or not. - _workspace.WorkspaceChanged += (_, _) => _updateQueue.AddWork(); + _ = _workspace.RegisterWorkspaceChangedHandler((_) => _updateQueue.AddWork()); } public async Task AddQueriesAsync(IGraphContext context, ImmutableArray graphQueries, CancellationToken disposalToken) diff --git a/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerViewModel.cs b/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerViewModel.cs index a3b29acfbc8e7..4856cebda2f50 100644 --- a/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerViewModel.cs +++ b/src/VisualStudio/Core/Def/StackTraceExplorer/StackTraceExplorerViewModel.cs @@ -5,8 +5,8 @@ using System.Collections.ObjectModel; using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.StackTraceExplorer; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.StackTraceExplorer; using Microsoft.VisualStudio.LanguageServices.Utilities; using Microsoft.VisualStudio.Text.Classification; @@ -53,7 +53,9 @@ public StackTraceExplorerViewModel(IThreadingContext threadingContext, Workspace _threadingContext = threadingContext; _workspace = workspace; - workspace.WorkspaceChanged += Workspace_WorkspaceChanged; + // Main thread dependency as Workspace_WorkspaceChanged modifies an ObservableCollection + _ = workspace.RegisterWorkspaceChangedHandler(Workspace_WorkspaceChanged, WorkspaceEventOptions.RequiresMainThreadOptions); + _classificationTypeMap = classificationTypeMap; _formatMap = formatMap; @@ -104,7 +106,7 @@ private void CallstackLines_CollectionChanged(object sender, System.Collections. NotifyPropertyChanged(nameof(IsInstructionTextVisible)); } - private void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void Workspace_WorkspaceChanged(WorkspaceChangeEventArgs e) { if (e.Kind == WorkspaceChangeKind.SolutionChanged) { diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs index 204e66d98aff2..104e7f2f922bf 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingToolWindow.cs @@ -3,11 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using Microsoft.VisualStudio.Shell; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using System.Diagnostics.CodeAnalysis; +using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.LanguageServices.ValueTracking; @@ -65,10 +65,10 @@ public void Initialize(ValueTrackingTreeViewModel viewModel, Workspace workspace _workspace = workspace; _threadingContext = threadingContext; - _workspace.WorkspaceChanged += OnWorkspaceChanged; + _ = _workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { Contract.ThrowIfFalse(Initialized); diff --git a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs index 2f9c4cde491f2..d58b016f39a64 100644 --- a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs @@ -262,6 +262,7 @@ private sealed class OpenSourceGeneratedFile : IDisposable private VisualStudioInfoBar.InfoBarMessage? _currentInfoBarMessage; private InfoBarInfo? _infoToShow = null; + private WorkspaceEventRegistration? _workspaceChangedDisposer; public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, SourceGeneratedDocumentIdentity documentIdentity) { @@ -284,7 +285,7 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff readOnlyRegionEdit.Apply(); } - this.Workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = this.Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); _batchingWorkQueue = new AsyncBatchingWorkQueue( TimeSpan.FromSeconds(1), @@ -311,7 +312,8 @@ public void Dispose() { _fileManager._threadingContext.ThrowIfNotOnUIThread(); - this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; // Disconnect the buffer from the workspace before making it eligible for edits DisconnectFromWorkspaceIfOpen(); @@ -422,7 +424,7 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) await EnsureWindowFrameInfoBarUpdatedAsync(cancellationToken).ConfigureAwait(true); } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { var projectId = _documentIdentity.DocumentId.ProjectId; diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 689670476e0a2..d35c2a1dcacad 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -61,7 +61,7 @@ public ProjectCodeModelFactory( Listener, threadingContext.DisposalToken); - _visualStudioWorkspace.WorkspaceChanged += OnWorkspaceChanged; + _ = _visualStudioWorkspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); } internal IAsynchronousOperationListener Listener { get; } @@ -153,7 +153,7 @@ static bool IsInputPending() } } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { // Events that can't change existing code model items. Can just ignore them. switch (e.Kind) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs index 53641c4e9701c..0b97c8eb01928 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzerItem/AnalyzerItemSource.cs @@ -37,6 +37,8 @@ internal sealed class AnalyzerItemSource : IAttachedCollectionSource private Workspace Workspace => _analyzersFolder.Workspace; private ProjectId ProjectId => _analyzersFolder.ProjectId; + private WorkspaceEventRegistration? _workspaceChangedDisposer; + public AnalyzerItemSource( AnalyzersFolderItem analyzersFolder, IAnalyzersCommandHandler commandHandler, @@ -51,7 +53,7 @@ public AnalyzerItemSource( listenerProvider.GetListener(FeatureAttribute.SourceGenerators), _cancellationTokenSource.Token); - this.Workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = this.Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); // Kick off the initial work to determine the starting set of items. _workQueue.AddWork(); @@ -64,7 +66,7 @@ public AnalyzerItemSource( public IEnumerable Items => _items; - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { switch (e.Kind) { @@ -93,7 +95,8 @@ private async ValueTask ProcessQueueAsync(CancellationToken cancellationToken) var project = this.Workspace.CurrentSolution.GetProject(this.ProjectId); if (project is null) { - this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; _cancellationTokenSource.Cancel(); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs index cdaeba7816eb8..1ca449b7deadd 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs @@ -36,6 +36,8 @@ internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttached protected ProjectId ProjectId { get; } protected IAnalyzersCommandHandler CommandHandler { get; } + private WorkspaceEventRegistration? _workspaceChangedDisposer; + /// /// The analyzer reference that has been found. Once it's been assigned a non-null value, it'll never be assigned /// again. @@ -74,7 +76,7 @@ protected AnalyzerReference? AnalyzerReference // Listen for changes that would affect the set of analyzers/generators in this reference, and kick off work // to now get the items for this source. - Workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspaceChangedDisposer = Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); _workQueue.AddWork(); } } @@ -98,7 +100,8 @@ private async ValueTask ProcessQueueAsync(CancellationToken cancellationToken) var project = this.Workspace.CurrentSolution.GetProject(this.ProjectId); if (project is null || !project.AnalyzerReferences.Contains(analyzerReference)) { - this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; _cancellationTokenSource.Cancel(); @@ -202,7 +205,7 @@ async Task> GetIdentitiesAsync() } } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { switch (e.Kind) { diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs index c36d8a75123d4..0525b9c7cc116 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs @@ -20,6 +20,8 @@ internal sealed partial class CpsDiagnosticItemSource : BaseDiagnosticAndGenerat private readonly IVsHierarchyItem _item; private readonly string _projectDirectoryPath; + private WorkspaceEventRegistration? _workspaceChangedDisposer; + public event PropertyChangedEventHandler? PropertyChanged; public CpsDiagnosticItemSource( @@ -45,7 +47,9 @@ public CpsDiagnosticItemSource( // then connect to it. if (workspace.CurrentSolution.ContainsProject(projectId)) { - Workspace.WorkspaceChanged += OnWorkspaceChangedLookForAnalyzer; + // Main thread dependency as OnWorkspaceChangedLookForAnalyzer accesses the IVsHierarchy + // and fires the PropertyChanged event + _workspaceChangedDisposer = Workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChangedLookForAnalyzer, WorkspaceEventOptions.RequiresMainThreadOptions); item.PropertyChanged += IVsHierarchyItem_PropertyChanged; // Now that we've subscribed, check once more in case we missed the event @@ -62,7 +66,9 @@ public CpsDiagnosticItemSource( private void UnsubscribeFromEvents() { - Workspace.WorkspaceChanged -= OnWorkspaceChangedLookForAnalyzer; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; + _item.PropertyChanged -= IVsHierarchyItem_PropertyChanged; } @@ -79,7 +85,7 @@ private void IVsHierarchyItem_PropertyChanged(object sender, PropertyChangedEven public override object SourceItem => _item; - private void OnWorkspaceChangedLookForAnalyzer(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChangedLookForAnalyzer(WorkspaceChangeEventArgs e) { // If the project has gone away in this change, it's not coming back, so we can stop looking at this point if (!e.NewSolution.ContainsProject(ProjectId)) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index 216cf417590c7..9ec77585af733 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -43,6 +43,7 @@ internal sealed class SourceGeneratedFileItemSource( private readonly CancellationSeries _cancellationSeries = new(); private ResettableDelay? _resettableDelay; + private WorkspaceEventRegistration? _workspaceChangedDisposer; public object SourceItem => _parentGeneratorItem; @@ -167,14 +168,13 @@ public void BeforeExpand() // in AfterCollapse, and _then_ subscribed here again. cancellationToken.ThrowIfCancellationRequested(); - _workspace.WorkspaceChanged += OnWorkpaceChanged; + _workspaceChangedDisposer = _workspace.RegisterWorkspaceChangedHandler(OnWorkspaceChanged); if (_workspace.CurrentSolution != solution) { // The workspace changed while we were doing our initial population, so // refresh it. We'll just call our OnWorkspaceChanged event handler // so this looks like any other change. - OnWorkpaceChanged(this, - new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionChanged, solution, _workspace.CurrentSolution)); + OnWorkspaceChanged(new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionChanged, solution, _workspace.CurrentSolution)); } } }, @@ -192,12 +192,13 @@ private void StopUpdating() lock (_gate) { _cancellationSeries.CreateNext(new CancellationToken(canceled: true)); - _workspace.WorkspaceChanged -= OnWorkpaceChanged; + _workspaceChangedDisposer?.Dispose(); + _workspaceChangedDisposer = null; _resettableDelay = null; } } - private void OnWorkpaceChanged(object sender, WorkspaceChangeEventArgs e) + private void OnWorkspaceChanged(WorkspaceChangeEventArgs e) { if (!e.NewSolution.ContainsProject(_parentGeneratorItem.ProjectId)) { @@ -216,7 +217,7 @@ private void OnWorkpaceChanged(object sender, WorkspaceChangeEventArgs e) { // Time to start the work all over again. We'll ensure any previous work is cancelled var cancellationToken = _cancellationSeries.CreateNext(); - var asyncToken = _asyncListener.BeginAsyncOperation(nameof(SourceGeneratedFileItemSource) + "." + nameof(OnWorkpaceChanged)); + var asyncToken = _asyncListener.BeginAsyncOperation(nameof(SourceGeneratedFileItemSource) + "." + nameof(OnWorkspaceChanged)); // We're going to go with a really long delay: once the user expands this we will keep it updated, but it's fairly // unlikely to change in a lot of cases if a generator only produces a stable set of names. diff --git a/src/Workspaces/Core/Portable/SemanticModelReuse/SemanticModelWorkspaceServiceFactory.SemanticModelWorkspaceService.cs b/src/Workspaces/Core/Portable/SemanticModelReuse/SemanticModelWorkspaceServiceFactory.SemanticModelWorkspaceService.cs index 5da26249d620b..8be7d9d051cdb 100644 --- a/src/Workspaces/Core/Portable/SemanticModelReuse/SemanticModelWorkspaceServiceFactory.SemanticModelWorkspaceService.cs +++ b/src/Workspaces/Core/Portable/SemanticModelReuse/SemanticModelWorkspaceServiceFactory.SemanticModelWorkspaceService.cs @@ -61,7 +61,7 @@ private sealed class SemanticModelReuseWorkspaceService : ISemanticModelReuseWor public SemanticModelReuseWorkspaceService(Workspace workspace) { _workspace = workspace; - _workspace.WorkspaceChanged += (_, e) => + _workspace.RegisterWorkspaceChangedHandler((e) => { // if our map points at documents not in the current solution, then we want to clear things out. // this way we don't hold onto semantic models past, say, the c#/vb solutions closing. @@ -78,7 +78,7 @@ public SemanticModelReuseWorkspaceService(Workspace workspace) return; } } - }; + }); } public async ValueTask ReuseExistingSpeculativeModelAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index d20005fd748bb..f16b7dbf2463a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -26,7 +26,8 @@ namespace Microsoft.CodeAnalysis; [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] public partial class Project { - // Only access these dictionaries when holding them as a lock + // Only access these dictionaries when holding them as a lock. These are intentionally simple dictionaries + // rather than ConcurrentDictionaries or ImmutableDictionaries for performance reasons. private Dictionary? _idToDocumentMap; private Dictionary? _idToSourceGeneratedDocumentMap; private Dictionary? _idToAdditionalDocumentMap; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 20f5a637864e8..a192c88d573fa 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -28,6 +28,8 @@ namespace Microsoft.CodeAnalysis; public partial class Solution { // Values for all these are created on demand. Only access when holding the dictionary as a lock. + // Intentionally a simple dictionary rather than a ConcurrentDictionary or ImmutableDictionary + // for performance reasons. private readonly Dictionary _projectIdToProjectMap = []; /// From 54481df62931af869d5169efdb0a009dfda0814c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 29 Apr 2025 09:20:15 -0700 Subject: [PATCH 114/114] Update Language Feature Status.md (#78358) --- docs/Language Feature Status.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index cd6f38c39f35c..692a885347bbe 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,9 +10,8 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | -| [User Defined Compound Assignment Operators](https://github.com/dotnet/csharplang/issues/9101) | [UserDefinedCompoundAssignment](https://github.com/dotnet/roslyn/tree/features/UserDefinedCompoundAssignment) | [In Progress](https://github.com/dotnet/roslyn/issues/76934) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | TBD | [AlekseyTs](https://github.com/AlekseyTs) | +| [User Defined Compound Assignment Operators](https://github.com/dotnet/csharplang/issues/9101) | [UserDefinedCompoundAssignment](https://github.com/dotnet/roslyn/tree/features/UserDefinedCompoundAssignment) | [In Progress](https://github.com/dotnet/roslyn/issues/76934) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [AlekseyTs](https://github.com/AlekseyTs) | | Runtime Async | [runtime-async](https://github.com/dotnet/roslyn/tree/features/runtime-async) | [In Progress](https://github.com/dotnet/roslyn/issues/75960) | [333fred](https://github.com/333fred) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | | | -| [Dictionary expressions](https://github.com/dotnet/csharplang/issues/8659) | [dictionary-expressions](https://github.com/dotnet/roslyn/tree/features/dictionary-expressions) | [In Progress](https://github.com/dotnet/roslyn/issues/76310) | [cston](https://github.com/cston), [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [`field` keyword in properties](https://github.com/dotnet/csharplang/issues/140) | [field-keyword](https://github.com/dotnet/roslyn/tree/features/field-keyword) | [Merged into 17.12p3](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313), [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [First-class Span Types](https://github.com/dotnet/csharplang/issues/7905) | [FirstClassSpan](https://github.com/dotnet/roslyn/tree/features/FirstClassSpan) | [Merged into 17.13p1](https://github.com/dotnet/roslyn/issues/73445) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [333fred](https://github.com/333fred) | | [333fred](https://github.com/333fred), [stephentoub](https://github.com/stephentoub) | | [Unbound generic types in `nameof`](https://github.com/dotnet/csharplang/issues/8480) | [PR](https://github.com/dotnet/roslyn/pull/75368) | [Merged into 17.13p2](https://github.com/dotnet/roslyn/pull/75368) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [AlekseyTs](https://github.com/AlekseyTs) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | @@ -22,6 +21,7 @@ efforts behind them. | [Extensions](https://github.com/dotnet/csharplang/issues/8697) | [extensions](https://github.com/dotnet/roslyn/tree/features/extensions) | [Preview merged into 17.14p3](https://github.com/dotnet/roslyn/issues/76130) | [jcouv](https://github.com/jcouv), [AlekseyTs](https://github.com/AlekseyTs) | [jjonescz](https://github.com/jjonescz), TBD | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [Null-conditional assignment](https://github.com/dotnet/csharplang/issues/6045) | [null-conditional-assignment](https://github.com/dotnet/roslyn/tree/features/null-conditional-assignment) | [Merged into 17.14p3](https://github.com/dotnet/roslyn/issues/75554) | [RikkiGibson](https://github.com/RikkiGibson) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [RikkiGibson](https://github.com/RikkiGibson) | | [Ignored directives](https://github.com/dotnet/csharplang/issues/8617) | [PR](https://github.com/dotnet/roslyn/pull/77696) | Merged into 17.14p3 | [jjonescz](https://github.com/jjonescz) | [RikkiGibson](https://github.com/RikkiGibson), [jaredpar](https://github.com/jaredpar) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jaredpar](https://github.com/jaredpar) | +| [Dictionary expressions](https://github.com/dotnet/csharplang/issues/8659) | [dictionary-expressions](https://github.com/dotnet/roslyn/tree/features/dictionary-expressions) | [Preview merged into 17.14p3](https://github.com/dotnet/roslyn/issues/76310) | [cston](https://github.com/cston), [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | # Working Set VB