From ea500de5dd6b6ac80edf175b1a8ca4e2af4832e5 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 29 Feb 2024 14:35:11 -0800 Subject: [PATCH 001/170] Bump patch version for NuGet publish purpsoe --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 3c81e1c73d88e..9035bbe250bf3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,7 +7,7 @@ 4 9 - 0 + 2 3 $(MajorVersion).$(MinorVersion).$(PatchVersion) + + Library + netstandard2.0 + Microsoft.CommonLanguageServerProtocol.Framework + + + false + + BINARY_COMPAT + + + + + + + diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs index 5df8580127331..7a602999dfb3b 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs @@ -10,7 +10,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Example; -public class ExampleLanguageServer : AbstractLanguageServer +internal class ExampleLanguageServer : AbstractLanguageServer { private readonly Action? _addExtraHandlers; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleRequestContext.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleRequestContext.cs index d1aab02c05235..9816f15f6deea 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleRequestContext.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleRequestContext.cs @@ -4,7 +4,7 @@ using Microsoft.CommonLanguageServerProtocol.Framework; -public class ExampleRequestContext +internal class ExampleRequestContext { public ILspServices LspServices; public ILspLogger Logger; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj index b3bc1079ab409..723eee38c7883 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/Microsoft.CommonLanguageServerProtocol.Framework.Example.csproj @@ -14,7 +14,10 @@ - + + + + diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/MultiRegisteringHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/MultiRegisteringHandler.cs index ce416b31a46fd..8a60594c16d6c 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/MultiRegisteringHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/MultiRegisteringHandler.cs @@ -8,7 +8,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Example; -public class MultiRegisteringHandler : +internal class MultiRegisteringHandler : IRequestHandler, IRequestHandler, INotificationHandler diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/NoOpLspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/NoOpLspLogger.cs index 55e6a48e3eda1..f1a63b308094d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/NoOpLspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/NoOpLspLogger.cs @@ -6,7 +6,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.UnitTests; -public sealed class NoOpLspLogger : AbstractLspLogger, ILspLogger +internal sealed class NoOpLspLogger : AbstractLspLogger, ILspLogger { public static NoOpLspLogger Instance = new(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestMethodHandlers.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestMethodHandlers.cs index 8c046019107c2..59ef8bc6dfd71 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestMethodHandlers.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestMethodHandlers.cs @@ -77,7 +77,7 @@ public Task HandleNotificationAsync(TestRequestContext requestContext, Cancellat } [LanguageServerEndpoint(Name, LanguageServerConstants.DefaultLanguageName)] -public class MutatingHandler : IRequestHandler +internal class MutatingHandler : IRequestHandler { public const string Name = "MutatingMethod"; public static readonly IMethodHandler Instance = new MutatingHandler(); @@ -96,7 +96,7 @@ public Task HandleRequestAsync(int request, TestRequestContext context, } [LanguageServerEndpoint(Name, LanguageServerConstants.DefaultLanguageName)] -public class CompletingHandler : IRequestHandler +internal class CompletingHandler : IRequestHandler { public const string Name = "CompletingMethod"; public static readonly IMethodHandler Instance = new CompletingHandler(); @@ -118,7 +118,7 @@ public async Task HandleRequestAsync(int request, TestRequestContext con } [LanguageServerEndpoint(Name, LanguageServerConstants.DefaultLanguageName)] -public class CancellingHandler : IRequestHandler +internal class CancellingHandler : IRequestHandler { public const string Name = "CancellingMethod"; public static readonly IMethodHandler Instance = new CancellingHandler(); @@ -137,7 +137,7 @@ public async Task HandleRequestAsync(int request, TestRequestContext con } [LanguageServerEndpoint(Name, LanguageServerConstants.DefaultLanguageName)] -public class ThrowingHandler : IRequestHandler +internal class ThrowingHandler : IRequestHandler { public const string Name = "ThrowingMethod"; public static readonly IMethodHandler Instance = new ThrowingHandler(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestRequestContext.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestRequestContext.cs index 490a838c95fef..f0ea9646dfeca 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestRequestContext.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/Mocks/TestRequestContext.cs @@ -7,9 +7,9 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.UnitTests; -public class TestRequestContext +internal class TestRequestContext { - public class Factory : AbstractRequestContextFactory + internal class Factory : AbstractRequestContextFactory { public static readonly Factory Instance = new(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs index 8da74e6f69401..4af41c4bd96bc 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Collections.Immutable; @@ -10,7 +13,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Manages handler discovery and distribution. /// +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractHandlerProvider +#else +internal abstract class AbstractHandlerProvider +#endif { /// /// Gets the s for all registered methods. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index ad8d5077fb960..4260996c47ff4 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Linq; using System.Reflection; @@ -11,10 +14,16 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractLanguageServer +#else +internal abstract class AbstractLanguageServer +#endif { private readonly JsonRpc _jsonRpc; +#pragma warning disable IDE1006 // Naming Styles - Required for API compat, TODO - https://github.com/dotnet/roslyn/issues/72251 protected readonly ILspLogger _logger; +#pragma warning restore IDE1006 // Naming Styles /// /// These are lazy to allow implementations to define custom variables that are used by @@ -168,10 +177,10 @@ private sealed class DelegatingEntryPoint private readonly string _method; private readonly AbstractLanguageServer _target; - private static readonly MethodInfo s_entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(EntryPointAsync)); - private static readonly MethodInfo s_parameterlessEntryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessEntryPointAsync)); - private static readonly MethodInfo s_notificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(NotificationEntryPointAsync)); - private static readonly MethodInfo s_parameterlessNotificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessNotificationEntryPointAsync)); + private static readonly MethodInfo s_entryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(EntryPointAsync))!; + private static readonly MethodInfo s_parameterlessEntryPointMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessEntryPointAsync))!; + private static readonly MethodInfo s_notificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(NotificationEntryPointAsync))!; + private static readonly MethodInfo s_parameterlessNotificationMethod = typeof(DelegatingEntryPoint).GetMethod(nameof(ParameterlessNotificationEntryPointAsync))!; public DelegatingEntryPoint(string method, AbstractLanguageServer target) { diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs index 3193fc8282f81..41c4bf4001219 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs @@ -2,11 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractLspLogger : ILspLogger +#else +internal abstract class AbstractLspLogger : ILspLogger +#endif { public abstract void LogDebug(string message, params object[] @params); public abstract void LogStartContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs index 3ec90ddbbf078..adcc85e1c484b 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System.Threading; using System.Threading.Tasks; @@ -18,7 +21,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// /// The type of the RequestContext to be used by the handler. +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractRequestContextFactory +#else +internal abstract class AbstractRequestContextFactory +#endif { /// /// Create a object from the given . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs index efd36dccd0e4b..7e5ea5c8aeb5a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs @@ -2,11 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractRequestScope(string name) : IDisposable +#else +internal abstract class AbstractRequestScope(string name) : IDisposable +#endif { public string Name { get; } = name; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs index cb60f7e6852f6..7df8edce2f545 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs @@ -2,9 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public abstract class AbstractTelemetryService +#else +internal abstract class AbstractTelemetryService +#endif { public abstract AbstractRequestScope CreateRequestScope(string lspMethodName); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BannedSymbols.txt b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BannedSymbols.txt new file mode 100644 index 0000000000000..42fd708fa29ec --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BannedSymbols.txt @@ -0,0 +1,73 @@ +T:Microsoft.VisualStudio.Shell.Interop.IComWrapper; Use Microsoft.VisualStudio.LanguageServices.Implementation.Interop.IComWrapperFixed instead +T:Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOptions; Use CSharpFormattingOptions2 instead +T:Microsoft.CodeAnalysis.Simplification.SimplificationOptions; Use SimplificationOptions2 instead +T:Microsoft.CodeAnalysis.CodeStyle.CodeStyleOption`1; Use 'Microsoft.CodeAnalysis.CodeStyle.CodeStyleOption2' instead +T:Microsoft.CodeAnalysis.CodeStyle.CodeStyleOptions; Use CodeStyleOptions2 instead +T:Microsoft.CodeAnalysis.Recommendations;RecommendationOptions; Use RecommendationOptions2 instead +T:Microsoft.CodeAnalysis.Formatting.FormattingOptions; Use FormattingOptions2 instead +T:Microsoft.CodeAnalysis.Options.PerLanguageOption`1; Use PerLanguageOption2 instead +T:Microsoft.CodeAnalysis.Options.Option`1; Use 'Microsoft.CodeAnalysis.Options.Option2' instead +T:Microsoft.CodeAnalysis.SyntaxWalker; Use SyntaxNode.DescendantNodes methods instead. +P:Microsoft.CodeAnalysis.Completion.CompletionContext.Options; Use CompletionOptions instead. +P:Microsoft.CodeAnalysis.Completion.CompletionItem.Properties; Use GetProperties instead. +M:Microsoft.CodeAnalysis.Completion.CompletionItem.WithProperties(System.Collections.Immutable.ImmutableDictionary{System.String,System.String}); Use ImmutableArray overload instead. +P:Microsoft.CodeAnalysis.Completion.CompletionList.Items; Use CompletionList.ItemsList instead +M:Microsoft.CodeAnalysis.Completion.CompletionProvider.ShouldTriggerCompletion(Microsoft.CodeAnalysis.Text.SourceText,System.Int32,Microsoft.CodeAnalysis.Completion.CompletionTrigger,Microsoft.CodeAnalysis.Options.OptionSet); Use internal overload instead +M:Microsoft.CodeAnalysis.Completion.CompletionProvider.GetDescriptionAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Completion.CompletionItem,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.Completion.CompletionService.FilterItems(Microsoft.CodeAnalysis.Document,System.Collections.Immutable.ImmutableArray{Microsoft.CodeAnalysis.Completion.CompletionItem},System.String); Use internal overload instead +M:Microsoft.CodeAnalysis.Completion.CompletionService.GetCompletionsAsync(Microsoft.CodeAnalysis.Document,System.Int32,Microsoft.CodeAnalysis.Completion.CompletionTrigger,System.Collections.Immutable.ImmutableHashSet{System.String},Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.Completion.CompletionService.GetDescriptionAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Completion.CompletionItem,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.Completion.CompletionService.GetRules; Use internal overload instead +M:Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document,System.Int32,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.CodeFixes.FixAllContext.#ctor(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider,Microsoft.CodeAnalysis.CodeFixes.FixAllScope,System.String,System.Collections.Generic.IEnumerable{System.String},Microsoft.CodeAnalysis.CodeFixes.FixAllContext.DiagnosticProvider,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.CodeFixes.FixAllContext.#ctor(Microsoft.CodeAnalysis.Document,System.Nullable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider,Microsoft.CodeAnalysis.CodeFixes.FixAllScope,System.String,System.Collections.Generic.IEnumerable{System.String},Microsoft.CodeAnalysis.CodeFixes.FixAllContext.DiagnosticProvider,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.CodeFixes.FixAllContext.#ctor(Microsoft.CodeAnalysis.Project,Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider,Microsoft.CodeAnalysis.CodeFixes.FixAllScope,System.String,System.Collections.Generic.IEnumerable{System.String},Microsoft.CodeAnalysis.CodeFixes.FixAllContext.DiagnosticProvider,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.Document.GetOptionsAsync(System.Threading.CancellationToken); Use Document.GetAnalyzerConfigOptionsAsync instead +T:Microsoft.CodeAnalysis.Options.DocumentOptionSet; Use AnalyzerConfigOptions instead +M:Microsoft.VisualStudio.Shell.ServiceExtensions.GetService``2(System.IServiceProvider); Use RoslynServiceExtensions instead. This extension internally relies on ThreadHelper, which is incompatible with testing. +M:Microsoft.VisualStudio.Shell.ServiceExtensions.GetService``2(System.IServiceProvider,System.Boolean); Use RoslynServiceExtensions instead. This extension internally relies on ThreadHelper, which is incompatible with testing +M:Microsoft.VisualStudio.Shell.ServiceExtensions.GetServiceAsync``2(Microsoft.VisualStudio.Shell.IAsyncServiceProvider); Use RoslynServiceExtensions instead. This extension internally relies on ThreadHelper, which is incompatible with testing +M:Microsoft.VisualStudio.Shell.ServiceExtensions.GetServiceAsync``2(Microsoft.VisualStudio.Shell.IAsyncServiceProvider,System.Boolean); Use RoslynServiceExtensions instead. This extension internally relies on ThreadHelper, which is incompatible with testing +P:Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory; Use IThreadingContext.JoinableTaskFactory instead. +M:Microsoft.CodeAnalysis.Formatting.Formatter.FormatAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use internal overload instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.FormatAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Text.TextSpan,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.FormatAsync(Microsoft.CodeAnalysis.Document,System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.FormatAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.SyntaxAnnotation,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.Format(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.SyntaxAnnotation,Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.Format(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.Format(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.Text.TextSpan,Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.Format(Microsoft.CodeAnalysis.SyntaxNode,System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.Text.TextSpan,Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(Microsoft.CodeAnalysis.SyntaxNode,System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.Workspace,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload with SyntaxFormattingOptions instead +M:Microsoft.CodeAnalysis.Formatting.Formatter.OrganizeImportsAsync(Microsoft.CodeAnalysis.Document,System.Threading.CancellationToken); Call IOrganizeImportsService.OrganizeImportsAsync directly +M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions +M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.SyntaxAnnotation,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions +M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,Microsoft.CodeAnalysis.Text.TextSpan,Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions +M:Microsoft.CodeAnalysis.Simplification.Simplifier.ReduceAsync(Microsoft.CodeAnalysis.Document,System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.Text.TextSpan},Microsoft.CodeAnalysis.Options.OptionSet,System.Threading.CancellationToken); Use overload that takes SimplifierOptions +M:Microsoft.CodeAnalysis.Editing.SyntaxEditor.#ctor(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.Host.HostWorkspaceServices); Use overload that takes HostSolutionServices instead +M:Microsoft.CodeAnalysis.FileTextLoader.#ctor(System.String,System.Text.Encoding); use WorkspaceFileTextLoader that calls on ITextFactoryService to create SourceText +M:Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SyntaxTree(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.ParseOptions,System.String,System.Text.Encoding); Use CSharpSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(System.String,Microsoft.CodeAnalysis.ParseOptions,System.String,System.Text.Encoding,System.Threading.CancellationToken); Use CSharpSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.CreateWithoutClone(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode); Use CSharpSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options, System.String path, System.Text.Encoding encoding); Use CSharpSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(System.String,Microsoft.CodeAnalysis.CSharp.CSharpParseOptions,System.String,System.Text.Encoding,System.Threading.CancellationToken); Use API that takes SourceText +M:Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(System.String,Microsoft.CodeAnalysis.CSharp.CSharpParseOptions,System.String,System.Text.Encoding,System.Collections.Immutable.ImmutableDictionary{System.String,Microsoft.CodeAnalysis.ReportDiagnostic},System.Nullable{System.Boolean},System.Threading.CancellationToken); Use API that takes SourceText +M:Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.SyntaxTree(Microsoft.CodeAnalysis.SyntaxNode,Microsoft.CodeAnalysis.ParseOptions,System.String,System.Text.Encoding); Use VisualBasicSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.ParseSyntaxTree(System.String,Microsoft.CodeAnalysis.ParseOptions,System.String,System.Text.Encoding,System.Threading.CancellationToken); Use overload with SourceText +M:Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.ParseSyntaxTree(System.String,Microsoft.CodeAnalysis.ParseOptions,System.String,System.Text.Encoding,System.Collections.Immutable.ImmutableDictionary{System.String,Microsoft.CodeAnalysis.ReportDiagnostic},System.Threading.CancellationToken); Use overload with SourceText +M:Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.CreateWithoutClone(Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode); Use VisualBasicSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.Create(Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode,Microsoft.CodeAnalysis.VisualBasic.VisualBasicParseOptions,System.String,System.Text.Encoding,System.Collections.Immutable.ImmutableDictionary{System.String,Microsoft.CodeAnalysis.ReportDiagnostic}); Use VisualBasicSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.Create(Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode,Microsoft.CodeAnalysis.VisualBasic.VisualBasicParseOptions,System.String,System.Text.Encoding); Use VisualBasicSyntaxTree sublass that takes checksum algorithm +M:Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.ParseText(System.String,Microsoft.CodeAnalysis.VisualBasic.VisualBasicParseOptions,System.String,System.Text.Encoding,System.Collections.Immutable.ImmutableDictionary{System.String,Microsoft.CodeAnalysis.ReportDiagnostic},System.Threading.CancellationToken); Use overload with SourceText +M:Microsoft.CodeAnalysis.Workspaces.Workspace.SetCurrentSolution(Microsoft.CodeAnalysis.Solution); Use SetCurrentSolutionEx instead. +M:System.Enum.GetHashCode(); Cast to integral type to avoid boxing on .NET Framework +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.PostProcessAsync(System.Collections.Generic.IEnumerable{Microsoft.CodeAnalysis.CodeActions.CodeActionOperation},System.Threading.CancellationToken); Use overload that takes a solution +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.PostProcessChangesAsync(Microsoft.CodeAnalysis.Solution,System.Threading.CancellationToken); Use overload that takes a solution +M:Microsoft.CodeAnalysis.CodeActions.CodeActionWithOptions.GetOperationsAsync(System.Object,System.Threading.CancellationToken); Use overload that takes a solution +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.GetOperationsAsync(System.Threading.CancellationToken); Use overload that takes a solution +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.GetPreviewOperationsAsync(System.Threading.CancellationToken); Use overload that takes a solution +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.ComputeOperationsAsync(System.Threading.CancellationToken); Use overload that takes progress +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.GetChangedSolutionAsync(CSystem.Threading.CancellationToken); Use overload that takes progress +M:Microsoft.CodeAnalysis.CodeActions.CodeAction.GetChangedDocumentAsync(CancellationToken); Use overload that takes progress +M:System.Diagnostics.Tracing.EventSource.WriteEvent(System.Int32,System.Object[]); Use WriteEventCore instead \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BreakingChanges.md b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BreakingChanges.md deleted file mode 100644 index 9ac6b0f33978c..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/BreakingChanges.md +++ /dev/null @@ -1,44 +0,0 @@ -# Handling breaking changes in CLaSP - -## How Versions will be handled - -We'll try to stick to SemVer, so breaking changes should result in an increase of the Major version. When there is an increase of the major version we will include both the previous and new version of the dll in the CLaSP VSIX for a time, during which partner teams will be expected to upgrade (to make sure we're only shipping usages of one of the dll's in VS). This might look like: - -```text -\CLaSP.vsix - \5.x.x - \Microsoft.CommonLanguageServerProtocol.Framework.dll (version 5.something) - \6.x.x - \Microsoft.CommonLanguageServerProtocol.Framework.dll (version 6.something) -``` - -During this time we'll have Binding redirects for any 5.x.x and below to the shipped 5.x.x version, and 6.0.0 and higher will redirect to 6.x.x. - -Once we have migrated all partners to the new version (or after warning and some time has passed) we will remove the old version from the VSIX (and it's accompanying redirect) leaving the shape as something like - -```text -\CLaSP.vsix - \6.x.x - \Microsoft.CommonLanguageServerProtocol.Framework.dll (version 6.something) -``` - -This is the model used by the `Microsoft.VisualStudio.LanguageServer.Protocol` package and they say they've had great success with this method. - -## Which changes will result in what update types - -When in doubt refer back to . - -|Change Type|Agreed Version update type| -|---|---| -|Addition of Class/Interface/etc|Minor| -|Removal of Class/interface/etc|Major| -|Addition/Removal of Member to Interface/Abstract|Major| -|New dependency if exposed|Major| -|New dependency if hidden|Minor?| -|Dependency version change if exposed|Major| -|Dependency version if upgrade is major|Major| -|Dep... not exposed|Minor| -|Change of Method signature|Major| -|Implementation changed|Patch (unless it has big effect on output)| - -Keep a log of breaking changes (a PR label may be ideal here). diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs index c31ab803225f5..ac0e0bd0ad03b 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Collections.Generic; using System.Collections.Immutable; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs index 820e5284d9d4b..aeb2a3d698f06 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs @@ -2,13 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System.Threading; using System.Threading.Tasks; namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialize", LanguageServerConstants.DefaultLanguageName)] +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public class InitializeHandler +#else +internal class InitializeHandler +#endif : IRequestHandler { private readonly IInitializeManager _capabilitiesManager; @@ -20,8 +27,6 @@ public InitializeHandler(IInitializeManager capabilitiesMan public bool MutatesSolutionState => true; - public bool RequiresLSPSolution => false; - public Task HandleRequestAsync(TRequest request, TRequestContext context, CancellationToken cancellationToken) { _capabilitiesManager.SetInitializeParams(request); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs index 0c58b45c19ecd..46f87a4c52df4 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Threading; using System.Threading.Tasks; @@ -9,14 +12,16 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialized", LanguageServerConstants.DefaultLanguageName)] +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public class InitializedHandler : INotificationHandler +#else +internal class InitializedHandler : INotificationHandler +#endif { private bool HasBeenInitialized = false; public bool MutatesSolutionState => true; - public bool RequiresLSPSolution => true; - public Task HandleNotificationAsync(TRequest request, TRequestContext requestContext, CancellationToken cancellationToken) { if (HasBeenInitialized) diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs index b93fae9685c29..837cd4b051de5 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Collections.Immutable; @@ -10,7 +13,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Manages handler discovery and distribution. /// +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IHandlerProvider +#else +internal interface IHandlerProvider +#endif { ImmutableArray GetRegisteredMethods(); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs index 2bddb76a55ef5..13e9fc9293a3e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs @@ -2,9 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IInitializeManager +#else +internal interface IInitializeManager +#endif { /// /// Gets a response to be used for "initialize", completing the negoticaitons between client and server. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs index 4f657579a7e7a..73b17b89df6ad 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System.Threading.Tasks; namespace Microsoft.CommonLanguageServerProtocol.Framework; @@ -11,7 +13,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An optional component to run additional logic when LSP shutdown and exit are called, /// for example logging messages, cleaning up custom resources, etc. /// +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface ILifeCycleManager +#else +internal interface ILifeCycleManager +#endif { /// /// Called when the server recieves the LSP exit notification. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs index 930ce4c6d939f..3bfe075fc3e0e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs @@ -2,11 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface ILspLogger +#else +internal interface ILspLogger +#endif { void LogStartContext(string message, params object[] @params); void LogEndContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs index 99febdc229d96..14ed351fb7e7c 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs @@ -2,14 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface ILspServices : IDisposable +#else +internal interface ILspServices : IDisposable +#endif { T GetRequiredService() where T : notnull; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs index fab7b6af7935c..d8299e27250a1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs @@ -2,12 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Top level type for LSP request handler. /// +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IMethodHandler +#else +internal interface IMethodHandler +#endif { /// /// Whether or not the solution state on the server is modified as a part of handling this request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs index ab20dc4500c44..22c8d8f2c38a1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs @@ -5,13 +5,20 @@ using System.Threading; using System.Threading.Tasks; +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// An interface for handlers of methods which do not return a response and receive no parameters. /// /// The type of the RequestContext to be used by this handler. +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface INotificationHandler : IMethodHandler +#else +internal interface INotificationHandler : IMethodHandler +#endif { Task HandleNotificationAsync(TRequestContext requestContext, CancellationToken cancellationToken); } @@ -21,7 +28,11 @@ public interface INotificationHandler : IMethodHandler /// /// The type of the Request parameter to be received. /// The type of the RequestContext to be used by this handler. +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface INotificationHandler : IMethodHandler +#else +internal interface INotificationHandler : IMethodHandler +#endif { Task HandleNotificationAsync(TRequest request, TRequestContext requestContext, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs index 51cf9a95f240b..832bbf4fdeae2 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs @@ -6,13 +6,20 @@ using System.Threading; using System.Threading.Tasks; +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// An item to be queued for execution. /// /// The type of the request context to be passed along to the handler. +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IQueueItem +#else +internal interface IQueueItem +#endif { /// /// Executes the work specified by this queue item. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs index c7d7898b8f03e..ee07e97cfdc0a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs @@ -6,6 +6,9 @@ using System.Threading; using System.Threading.Tasks; +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; /// @@ -20,7 +23,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// The type of the RequestContext to be used by the handler. [Obsolete($"Use {nameof(AbstractRequestContextFactory)} instead.", error: false)] +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IRequestContextFactory +#else +internal interface IRequestContextFactory +#endif { /// /// Create a object from the given . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs index 5f36c4e8477b3..bae2a3c7ff1e8 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Threading; using System.Threading.Tasks; @@ -12,7 +15,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// Queues requests to be executed in the proper order. /// /// The type of the RequestContext to be used by the handler. +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IRequestExecutionQueue : IAsyncDisposable +#else +internal interface IRequestExecutionQueue : IAsyncDisposable +#endif { /// /// Queue a request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs index cbdff1e945242..2bc711aabe3a2 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs @@ -2,14 +2,19 @@ // 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; +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System.Threading; using System.Threading.Tasks; -using Microsoft.CommonLanguageServerProtocol.Framework; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IRequestHandler : IMethodHandler +#else +internal interface IRequestHandler : IMethodHandler +#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. @@ -21,7 +26,11 @@ public interface IRequestHandler : IMethod Task HandleRequestAsync(TRequest request, TRequestContext context, CancellationToken cancellationToken); } +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface IRequestHandler : IMethodHandler +#else +internal interface IRequestHandler : IMethodHandler +#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs index 542bb422576a4..eaf30967bc0b9 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs @@ -2,9 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler +#else +internal interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler +#endif { /// /// Gets the identifier of the document from the request, if the request provides one. @@ -12,6 +19,10 @@ public interface ITextDocumentIdentifierHandler /// Default language name for use with and . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index db159f41e5403..1f8c71ce2d7ab 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Linq; @@ -11,7 +14,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An attribute which identifies the method which an implements. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false)] +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public class LanguageServerEndpointAttribute : Attribute +#else +internal class LanguageServerEndpointAttribute : Attribute +#endif { /// /// Contains the method that this implements. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj new file mode 100644 index 0000000000000..5fc44385be687 --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Package.csproj @@ -0,0 +1,45 @@ + + + + + + + Library + netstandard2.0 + + + true + true + Microsoft.CommonLanguageServerProtocol.Framework + false + + A framework of sources for building Language Server Protocol implementations in C#. + + + $(NoWarn);NU5128 + + true + + + + + + + + + + + + + + + true + contentFiles\cs\$(TargetFramework)\ + + + diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems new file mode 100644 index 0000000000000..3e94a73112030 --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems @@ -0,0 +1,42 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 64eaded3-4b5d-4431-bbe5-a4aba1c38c00 + + + Microsoft.CommonLanguageServerProtocol.Framework.Shared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj new file mode 100644 index 0000000000000..cc3aae4558d86 --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj @@ -0,0 +1,16 @@ + + + + 64eaded3-4b5d-4431-bbe5-a4aba1c38c00 + 14.0 + + + + + + + + + + + \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.csproj deleted file mode 100644 index 4e6abaa06ddaf..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Library - netstandard2.0 - true - - A framework for building Language Server Protocol implementations in C#. - - - - - - - - - - - - - - - diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs index fce873b6c73d7..5cf8e93cc2383 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Linq; using System.Threading; @@ -113,6 +116,8 @@ public async Task CreateRequestContextAsync(IMethodHandler hand /// representing the task that the client is waiting for, then re-thrown so that /// the queue can correctly handle them depending on the type of request. /// + /// Context used for the request. + /// The handler to execute. /// /// The result of the request. public async Task StartRequestAsync(TRequestContext? context, IMethodHandler handler, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs index 3f982243d2aad..ad65a6f58fbd5 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Collections.Concurrent; using System.Diagnostics; @@ -46,7 +49,11 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// more messages, and a new queue will need to be created. /// /// +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public class RequestExecutionQueue : IRequestExecutionQueue +#else +internal class RequestExecutionQueue : IRequestExecutionQueue +#endif { protected readonly ILspLogger _logger; protected readonly AbstractHandlerProvider _handlerProvider; @@ -112,6 +119,7 @@ public void Start() /// /// The request to handle. /// The name of the LSP method. + /// The set of LSP services to use. /// A cancellation token that will cancel the handing of this request. /// The request could also be cancelled by the queue shutting down. /// A task that can be awaited to observe the results of the handing of this request. @@ -311,6 +319,7 @@ protected virtual IMethodHandler GetHandlerForRequest(IQueueItem /// The task to be inspected. + /// If exceptions should be re-thrown. /// The task from , to allow chained calls if needed. public virtual Task WrapStartRequestTaskAsync(Task nonMutatingRequestTask, bool rethrowExceptions) { diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs index 94af09271f6bc..a4e41c02d182b 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs @@ -2,8 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language); +#else +internal record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language); +#endif diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs index ce7a816dfe9f4..e689d852425f1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs @@ -2,11 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; +#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 public class RequestShutdownEventArgs : EventArgs +#else +internal class RequestShutdownEventArgs : EventArgs +#endif { public string Message { get; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs index 7ffc525c5539b..120b54dfb0c21 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs @@ -2,6 +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. +// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable +#nullable enable + using System; using System.Collections.Immutable; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig new file mode 100644 index 0000000000000..3bd53642047ae --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/contentFiles/.editorconfig @@ -0,0 +1,10 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +# We don't want any analyzer diagnostics to be reported for people consuming this as a source package. +dotnet_analyzer_diagnostic.severity = none + +generated_code = true \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.cs.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.cs.xlf deleted file mode 100644 index 79b5f25b69e1d..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.cs.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Nejde deserializovat identifikátor Uri. Zjistila se neočekávaná hodnota: {0}. - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Nejde deserializovat MarkupContent. Zjistila se neočekávaná hodnota: {0}. - - - - A SumType cannot have another SumType as type parameter. - Typ SumType nemůže mít jako parametr typu jiný typ SumType. - - - - None of the SumType type parameters could be deserialized - Žádný z parametrů typu SumType nelze deserializovat. - - - - Type {0} is missing a contructor that takes a single string as parameter. - U typu {0} chybí konstruktor, který jako parametr přebírá jeden řetězec. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Nedá se deserializovat výčet založený na řetězci. Zjistila se neočekávaná data: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Nejde deserializovat TextDocumentSyncOptions. Zjistila se neočekávaná hodnota: {0}. - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.de.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.de.xlf deleted file mode 100644 index 8f9ddabcc3a44..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.de.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - URI kann nicht deserialisiert werden kann. Unerwarteter Wert erkannt: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - MarkupContent kann nicht deserialisiert werden. Unerwarteter Wert erkannt: {0} - - - - A SumType cannot have another SumType as type parameter. - Ein „SumType“ kann keinen anderen „SumType“ als Typparameter aufweisen. - - - - None of the SumType type parameters could be deserialized - Keiner der Parameter vom Typ „SumType“ konnte deserialisiert werden - - - - Type {0} is missing a contructor that takes a single string as parameter. - Für den Typ {0} fehlt ein Konstruktor, der eine einzelne Zeichenfolge als Parameter akzeptiert. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Die zeichenfolgenbasierte Enumeration kann nicht deserialisiert werden. Unerwartete Daten: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - TextDocumentSyncOptions kann nicht deserialisiert werden. Unerwarteter Wert erkannt: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.es.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.es.xlf deleted file mode 100644 index f453befd9ca20..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.es.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - No se puede deserializar el URI. Se encontró un valor inesperado: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - No se puede deserializar MarkupContent. Se encontró un valor inesperado: {0} - - - - A SumType cannot have another SumType as type parameter. - Un SumType no puede tener otro SumType como parámetro de tipo. - - - - None of the SumType type parameters could be deserialized - No se pudo deserializar ninguno de los parámetros de tipo SumType - - - - Type {0} is missing a contructor that takes a single string as parameter. - Al tipo {0} le falta un contructor que toma una sola cadena como parámetro. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - No se puede deserializar la enumeración basada en cadenas. Se encontraron datos inesperados: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - No se puede deserializar TextDocumentSyncOptions. Se encontró un valor inesperado: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.fr.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.fr.xlf deleted file mode 100644 index 5e7e2defcd43f..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.fr.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Impossible de désérialiser l'URI. Valeur inattendue rencontrée : {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Impossible de désérialiser MarkupContent. Valeur inattendue rencontrée : {0} - - - - A SumType cannot have another SumType as type parameter. - Un SumType ne peut pas avoir un autre SumType comme paramètre de type. - - - - None of the SumType type parameters could be deserialized - Aucun des paramètres de type SumType n’a pu être désérialisé. - - - - Type {0} is missing a contructor that takes a single string as parameter. - Il manque un constructeur de type {0} qui accepte une seule chaîne comme paramètre. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Impossible de désérialiser l’énumération basée sur une chaîne. Données inattendues rencontrées : {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Impossible de désérialiser TextDocumentSyncOptions. Valeur inattendue rencontrée : {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.it.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.it.xlf deleted file mode 100644 index 379ebf409cd07..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.it.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Non è possibile deserializzare Uri. È stato rilevato il valore imprevisto {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Non è possibile deserializzare MarkupContent. È stato rilevato il valore imprevisto {0} - - - - A SumType cannot have another SumType as type parameter. - Un SumType non può avere un altro SumType come parametro di tipo. - - - - None of the SumType type parameters could be deserialized - Nessuno dei parametri di tipo SumType può essere deserializzato - - - - Type {0} is missing a contructor that takes a single string as parameter. - Nel tipo {0} manca un costruttore che accetta una singola stringa come parametro. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Impossibile deserializzare l'enumerazione basata su stringhe. Rilevati dati imprevisti: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Non è possibile deserializzare TextDocumentSyncOptions. È stato rilevato il valore imprevisto {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ja.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ja.xlf deleted file mode 100644 index 6e4c76df587e6..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ja.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - URI を逆シリアル化できません。予期しない値が発生しました: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - MarkupContent を逆シリアル化できません。予期しない値が発生しました: {0} - - - - A SumType cannot have another SumType as type parameter. - SumType に型パラメーターとして別の SumType を指定することはできません。 - - - - None of the SumType type parameters could be deserialized - どの SumType 型パラメーターも逆シリアル化できませんでした - - - - Type {0} is missing a contructor that takes a single string as parameter. - パラメーターとして単一の文字列を取るコントラクターが 型 {0} にありません。 - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - 文字列ベースの列挙型を逆シリアル化できません。予期しないデータが検出されました: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - TextDocumentSyncOptions を逆シリアル化できません。予期しない値が発生しました: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ko.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ko.xlf deleted file mode 100644 index fe6d6b15e95b6..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ko.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - URI를 역직렬화할 수 없습니다. 예기치 않은 값 {0}이(가) 있습니다. - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - MarkupContent를 역직렬화할 수 없습니다. 예기치 않은 값 {0}이(가) 있습니다. - - - - A SumType cannot have another SumType as type parameter. - SumType에는 다른 SumType을 형식 매개 변수로 사용할 수 없습니다. - - - - None of the SumType type parameters could be deserialized - SumType 형식 매개 변수를 역직렬화할 수 없습니다. - - - - Type {0} is missing a contructor that takes a single string as parameter. - {0} 유형에 단일 문자열을 매개 변수로 사용하는 생성자가 없습니다. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - 문자열 기반 열거형을 역직렬화할 수 없습니다. 예상치 못한 데이터 발생: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - TextDocumentSyncOptions를 역직렬화할 수 없습니다. 예기치 않은 값 {0}이(가) 있습니다. - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pl.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pl.xlf deleted file mode 100644 index ec0d884105ec5..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pl.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Nie można zdeserializować identyfikatora Uri. Napotkano nieoczekiwaną wartość: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Nie można zdeserializować elementu MarkupContent. Napotkano nieoczekiwaną wartość: {0} - - - - A SumType cannot have another SumType as type parameter. - Typ SumType nie może mieć innego typu SumType jako parametru typu. - - - - None of the SumType type parameters could be deserialized - Nie można zdeserializować żadnego z parametrów typu SumType - - - - Type {0} is missing a contructor that takes a single string as parameter. - W typie {0} brakuje konstruktora, który przyjmuje pojedynczy ciąg jako parametr. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Nie można zdeserializować wyliczenia opartego na ciągach. Napotkano nieoczekiwane dane: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Nie można zdeserializować elementu TextDocumentSyncOptions. Napotkano nieoczekiwaną wartość: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pt-BR.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pt-BR.xlf deleted file mode 100644 index 618e0879d9f6b..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.pt-BR.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Não é possível desserializar o Uri. Valor inesperado encontrado: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Não é possível desserializar MarkupContent. Valor inesperado encontrado: {0} - - - - A SumType cannot have another SumType as type parameter. - Um SumType não pode ter outro SumType como parâmetro de tipo. - - - - None of the SumType type parameters could be deserialized - Nenhum dos parâmetros do tipo SumType pode ser desserializado - - - - Type {0} is missing a contructor that takes a single string as parameter. - O tipo {0} falta um construtor que toma uma única corda como parâmetro. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Não é possível desserializar enumeração baseada em cadeia de caracteres. Dados inesperados encontrados: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Não é possível desserializar TextDocumentSyncOptions. Valor inesperado encontrado: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ru.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ru.xlf deleted file mode 100644 index 33fcc20f8bfd1..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.ru.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - Не удалось десериализовать URI. Возникло непредвиденное значение: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - Не удалось десериализовать MarkupContent. Возникло непредвиденное значение: {0} - - - - A SumType cannot have another SumType as type parameter. - SumType не может иметь другой SumType в качестве параметра типа. - - - - None of the SumType type parameters could be deserialized - Невозможно десериализировать ни один из параметров типа SumType - - - - Type {0} is missing a contructor that takes a single string as parameter. - В типе {0} отсутствует конструктор, принимающий одну строку в качестве параметра. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Не удалось десериализовать перечисление на основе строк. Обнаружены непредвиденные данные: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - Не удалось десериализовать TextDocumentSyncOptions. Возникло непредвиденное значение: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.tr.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.tr.xlf deleted file mode 100644 index 51564d4b1adb5..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.tr.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - URI seri durumdan çıkarılamıyor. Beklenmeyen değerle karşılaşıldı: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - MarkupContent seri durumdan çıkarılamıyor. Beklenmeyen değerle karşılaşıldı: {0} - - - - A SumType cannot have another SumType as type parameter. - SumType, tür parametresi olarak başka bir SumType'a sahip olamaz. - - - - None of the SumType type parameters could be deserialized - SumType tür parametrelerinin hiçbiri seri durumdan çıkarılamadı - - - - Type {0} is missing a contructor that takes a single string as parameter. - {0} türünde parametre olarak tek bir dize alan bir yapıcı yok. - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - Dize tabanlı sabit listesi seri durumdan çıkarılamıyor. Beklenmeyen veriyle karşılaşıldı: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - TextDocumentSyncOptions seri durumdan çıkarılamıyor. Beklenmeyen değerle karşılaşıldı: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hans.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hans.xlf deleted file mode 100644 index 5f5fec71c7d25..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hans.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - 无法反序列化 URI。出现意外值: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - 无法反序列化 MarkupContent。出现意外值: {0} - - - - A SumType cannot have another SumType as type parameter. - SumType 不能将另一个 SumType 作为类型参数。 - - - - None of the SumType type parameters could be deserialized - 无法反序列化任何 SumType 类型参数 - - - - Type {0} is missing a contructor that takes a single string as parameter. - 类型 {0} 缺少一个将单个字符串用作参数的构造函数。 - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - 无法反序列化基于字符串的枚举。遇到意外数据: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - 无法反序列化 TextDocumentSyncOptions。出现意外值: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hant.xlf b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hant.xlf deleted file mode 100644 index b2cd9ad11bfc1..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/xlf/LSPFrameworkResources.zh-Hant.xlf +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Unable to deserialize Uri. Unexpected value encountered: {0} - 無法將 URI 還原序列化。發現非預期的值: {0} - - - - Unable to deserialize MarkupContent. Unexpected value encountered: {0} - 無法將 MarkupContent 還原序列化。發現非預期的值: {0} - - - - A SumType cannot have another SumType as type parameter. - SumType 不能有另一個 SumType 做為型別參數。 - - - - None of the SumType type parameters could be deserialized - 沒有可還原序列化的 SumType 型別參數 - - - - Type {0} is missing a contructor that takes a single string as parameter. - {0} 類型缺少使用單一字串做為參數的建構函式。 - - - - Unable to deserialize string-based enum. Unexpected data encountered: {0} - 無法將字串型列舉還原序列化。發現未預期的資料: {0} - - - - Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0} - 無法將 TextDocumentSyncOptions 還原序列化。發現非預期的值: {0} - - - - - \ No newline at end of file diff --git a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 2d42aa74a73b4..df5d1f713bcd7 100644 --- a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -15,10 +15,10 @@ - + @@ -70,13 +70,13 @@ - + - - - - - + + + + + @@ -104,4 +104,6 @@ + + diff --git a/src/Tools/ExternalAccess/CompilerDeveloperSDK/Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.csproj b/src/Tools/ExternalAccess/CompilerDeveloperSDK/Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.csproj index b7f896ff02213..d567a1b9dea0a 100644 --- a/src/Tools/ExternalAccess/CompilerDeveloperSDK/Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.csproj +++ b/src/Tools/ExternalAccess/CompilerDeveloperSDK/Microsoft.CodeAnalysis.ExternalAccess.CompilerDeveloperSDK.csproj @@ -20,7 +20,6 @@ - diff --git a/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj b/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj index a935edd881c38..370d875201436 100644 --- a/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj +++ b/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj @@ -15,6 +15,10 @@ + + + + - + https://github.com/dotnet/source-build-externals - 16ece46ae00dd1e8ac30a360d3dd03a6a682db41 + 472629e451a5a87410ea3670606f7235a4dd5a02 From 5f4a962cf3725cc75029b850e4c3dc47a3f91a62 Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Tue, 12 Mar 2024 13:10:16 -0400 Subject: [PATCH 012/170] Do not put an equals and quotes when completing an xml attribute if present --- ...mentationCommentCompletionProviderTests.cs | 29 +++++++++++++++++++ .../XmlDocCommentCompletionProvider.cs | 2 +- .../AbstractDocCommentCompletionProvider.cs | 4 +-- .../XmlDocCommentCompletionProvider.vb | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 9c6dd8ca0eedc..94ca88f585996 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -760,6 +760,35 @@ static void Goo() """, "cref", "langword", "href"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72259")] + public async Task SeeAttributeNames2() + { + var text = """ + class C + { + /// + /// + /// + static void Goo() + { + } + } + """; + var expected = """ + class C + { + /// + /// + /// + static void Goo() + { + } + } + """; + + await VerifyProviderCommitAsync(text, "langword", expected, null); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37504")] public async Task SeeAlsoAttributeNames() { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index 0b9617d7fa77f..dd6028ec8a342 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -85,7 +85,7 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition, if (IsAttributeNameContext(token, position, out var elementName, out var existingAttributes)) { - return GetAttributeItems(elementName, existingAttributes); + return GetAttributeItems(elementName, existingAttributes, !token.GetNextToken().IsKind(SyntaxKind.EqualsToken)); } var wasTriggeredAfterSpace = trigger.Kind == CompletionTriggerKind.Insertion && trigger.Character == ' '; diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 4f2066f4253d4..3ee0db19310bc 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -113,10 +113,10 @@ private CompletionItem GetItem(string name) return CreateCompletionItem(name); } - protected IEnumerable GetAttributeItems(string tagName, ISet existingAttributes) + protected IEnumerable GetAttributeItems(string tagName, ISet existingAttributes, bool addEqualsAndQuotes) { return s_attributeMap.Where(x => x.elementName == tagName && !existingAttributes.Contains(x.attributeName)) - .Select(x => CreateCompletionItem(x.attributeName, beforeCaretText: x.text, afterCaretText: "\"")); + .Select(x => CreateCompletionItem(x.attributeName, beforeCaretText: addEqualsAndQuotes ? x.text : x.text[..^2], afterCaretText: addEqualsAndQuotes ? "\"" : "")); } protected IEnumerable GetAlwaysVisibleItems() diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 576a4674aef5d..90fd3b9404964 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -332,7 +332,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Private Function GetAttributes(tagName As String, attributes As SyntaxList(Of XmlNodeSyntax)) As IEnumerable(Of CompletionItem) Dim existingAttributeNames = attributes.Select(AddressOf GetAttributeName).WhereNotNull().ToSet() - Return GetAttributeItems(tagName, existingAttributeNames) + Return GetAttributeItems(tagName, existingAttributeNames, false) End Function Private Shared Function GetAttributeName(node As XmlNodeSyntax) As String From aa638970252d0b22ced6e979a10e036811b8247e Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 12 Mar 2024 10:24:58 -0700 Subject: [PATCH 013/170] Use the project cone information in Scope.FindAssetsAsync This avoids full solution level searches in scenarios where a project cone is being searched. This was particularly bad when the AssetHint didn't indicate a project/document. --- .../Remote/Core/SolutionAssetStorage.Scope.cs | 17 +++++++++-------- .../Remote/Core/SolutionAssetStorage.cs | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index aa549b72272c8..7e989a7a72509 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -18,11 +18,13 @@ internal partial class SolutionAssetStorage internal sealed partial class Scope( SolutionAssetStorage storage, Checksum solutionChecksum, + ProjectId? projectId, SolutionCompilationState compilationState) : IDisposable { private readonly SolutionAssetStorage _storage = storage; public readonly Checksum SolutionChecksum = solutionChecksum; + public readonly ProjectId? ProjectId = projectId; public readonly SolutionCompilationState CompilationState = compilationState; /// @@ -67,15 +69,14 @@ private async Task FindAssetsAsync( AssetHint assetHint, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) { var solutionState = this.CompilationState; - if (solutionState.TryGetStateChecksums(out var stateChecksums)) - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); - - foreach (var projectId in solutionState.SolutionState.ProjectIds) + if (ProjectId is null) { - if (remainingChecksumsToFind.Count == 0) - break; - - if (solutionState.TryGetStateChecksums(projectId, out stateChecksums)) + if (solutionState.TryGetStateChecksums(out var stateChecksums)) + await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + } + else + { + if (solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)) await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 061c9b4ddd33e..0287a3dee74e2 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -76,7 +76,7 @@ public async ValueTask StoreAssetsAsync(SolutionCompilationState compilat return scope; } - scope = new Scope(this, checksum, compilationState); + scope = new Scope(this, checksum, projectId, compilationState); _checksumToScope[checksum] = scope; return scope; } From 95b7e3835db5ca0cb3e70e7c4c576c6ffa54152b Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 12 Mar 2024 10:35:34 -0700 Subject: [PATCH 014/170] Use contract to verify the projectId is valid in the solutionstate --- src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 7e989a7a72509..cc06e923017f2 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -76,8 +76,9 @@ private async Task FindAssetsAsync( } else { - if (solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)) - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)); + + await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } } From efd7530d37d724b7db3f04ecafd2118f84ef3f24 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 12 Mar 2024 10:44:28 -0700 Subject: [PATCH 015/170] Contract verify the non-project cone case too --- .../Remote/Core/SolutionAssetStorage.Scope.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index cc06e923017f2..301a0075ac622 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -69,17 +69,16 @@ private async Task FindAssetsAsync( AssetHint assetHint, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) { var solutionState = this.CompilationState; + SolutionCompilationStateChecksums? stateChecksums; + if (ProjectId is null) - { - if (solutionState.TryGetStateChecksums(out var stateChecksums)) - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); - } + solutionState.TryGetStateChecksums(out stateChecksums); else - { - Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)); + solutionState.TryGetStateChecksums(ProjectId, out stateChecksums); - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); - } + Contract.ThrowIfNull(stateChecksums); + + await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } public TestAccessor GetTestAccessor() From b26cf9557988ea20ce88d3cfe4f1125489ca3f7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 11:33:41 -0700 Subject: [PATCH 016/170] Add more project cone info --- .../Solution/SolutionCompilationState.cs | 9 ++- .../SolutionCompilationState_Checksum.cs | 77 +++++++++++++------ .../Workspace/Solution/SolutionState.cs | 9 ++- .../Solution/SolutionState_Checksum.cs | 64 ++++++++------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 17 ++-- 5 files changed, 113 insertions(+), 63 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 524b1c7ea9e3a..63c06209616af 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -69,9 +69,12 @@ private SolutionCompilationState( FrozenSourceGeneratedDocumentStates = frozenSourceGeneratedDocumentStates; // when solution state is changed, we recalculate its checksum - _lazyChecksums = AsyncLazy.Create(static (self, c) => - self.ComputeChecksumsAsync(projectId: null, c), - arg: this); + _lazyChecksums = AsyncLazy.Create(static async (self, cancellationToken) => + { + var (checksums, projectCone) = await self.ComputeChecksumsAsync(projectId: null, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue(projectCone != null); + return checksums; + }, arg: this); _cachedFrozenSnapshot = cachedFrozenSnapshot ?? AsyncLazy.Create(synchronousComputeFunction: static (self, c) => self.ComputeFrozenSnapshot(c), diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index dd7d83c0b8e4b..a730edce084aa 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -14,6 +14,18 @@ namespace Microsoft.CodeAnalysis; +internal readonly struct ProjectCone +{ + public readonly ProjectId RootProjectId; + public readonly IReadOnlySet ProjectIds; + + public ProjectCone(ProjectId rootProjectId, IReadOnlySet projectIds) + { + RootProjectId = rootProjectId; + ProjectIds = projectIds; + } +} + internal partial class SolutionCompilationState { /// @@ -27,25 +39,32 @@ internal partial class SolutionCompilationState /// Mapping from project-id to the checksums needed to synchronize it over to an OOP host. Lock this specific /// field before reading/writing to it. /// - private readonly Dictionary> _lazyProjectChecksums = []; + private readonly Dictionary> _lazyProjectChecksums = []; public bool TryGetStateChecksums([NotNullWhen(true)] out SolutionCompilationStateChecksums? stateChecksums) => _lazyChecksums.TryGetValue(out stateChecksums); public bool TryGetStateChecksums(ProjectId projectId, [NotNullWhen(true)] out SolutionCompilationStateChecksums? stateChecksums) { - AsyncLazy? checksums; + AsyncLazy<(SolutionCompilationStateChecksums checksums, ProjectCone projectCone)>? lazyChecksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums) || - checksums == null) + if (!_lazyProjectChecksums.TryGetValue(projectId, out lazyChecksums) || + lazyChecksums == null) { stateChecksums = null; return false; } } - return checksums.TryGetValue(out stateChecksums); + if (lazyChecksums.TryGetValue(out var checksumsAndCone)) + { + stateChecksums = null; + return false; + } + + stateChecksums = checksumsAndCone.checksums; + return true; } public Task GetStateChecksumsAsync(CancellationToken cancellationToken) @@ -58,22 +77,25 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken } /// Gets the checksum for only the requested project (and any project it depends on) - public async Task GetStateChecksumsAsync( - ProjectId projectId, + public async Task<(SolutionCompilationStateChecksums checksums, ProjectCone projectCone)> GetStateChecksumsAsync( + ProjectId projectConeId, CancellationToken cancellationToken) { - Contract.ThrowIfNull(projectId); + Contract.ThrowIfNull(projectConeId); - AsyncLazy? checksums; + AsyncLazy<(SolutionCompilationStateChecksums checksums, ProjectCone projectCone)>? checksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums)) + if (!_lazyProjectChecksums.TryGetValue(projectConeId, out checksums)) { - checksums = AsyncLazy.Create(static (arg, c) => - arg.self.ComputeChecksumsAsync(arg.projectId, c), - arg: (self: this, projectId)); + checksums = AsyncLazy.Create(static async (arg, cancellationToken) => + { + var (checksum, projectCone) = await arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(projectCone); + return (checksum, projectCone.Value); + }, arg: (self: this, projectConeId)); - _lazyProjectChecksums.Add(projectId, checksums); + _lazyProjectChecksums.Add(projectConeId, checksums); } } @@ -82,13 +104,13 @@ public async Task GetStateChecksumsAsync( } /// Gets the checksum for only the requested project (and any project it depends on) - public async Task GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) + public async Task<(Checksum checksum, ProjectCone projectCone)> GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) { - var checksums = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - return checksums.Checksum; + var (checksums, projectCone) = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + return (checksums.Checksum, projectCone); } - private async Task ComputeChecksumsAsync( + private async Task<(SolutionCompilationStateChecksums checksums, ProjectCone? projectCone)> ComputeChecksumsAsync( ProjectId? projectId, CancellationToken cancellationToken) { @@ -96,9 +118,19 @@ private async Task ComputeChecksumsAsync( { using (Logger.LogBlock(FunctionId.SolutionCompilationState_ComputeChecksumsAsync, this.SolutionState.FilePath, cancellationToken)) { - var solutionStateChecksum = projectId == null - ? await this.SolutionState.GetChecksumAsync(cancellationToken).ConfigureAwait(false) - : await this.SolutionState.GetChecksumAsync(projectId, cancellationToken).ConfigureAwait(false); + Checksum solutionStateChecksum; + ProjectCone? projectCone; + + if (projectId is null) + { + solutionStateChecksum = await this.SolutionState.GetChecksumAsync(cancellationToken).ConfigureAwait(false); + projectCone = null; + } + else + { + (var stateChecksums, projectCone) = await this.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + solutionStateChecksum = stateChecksums.Checksum; + } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; ChecksumsAndIds? frozenSourceGeneratedDocuments = null; @@ -112,10 +144,11 @@ private async Task ComputeChecksumsAsync( frozenSourceGeneratedDocuments = await FrozenSourceGeneratedDocumentStates.Value.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); } - return new SolutionCompilationStateChecksums( + var compilationStateChecksums = new SolutionCompilationStateChecksums( solutionStateChecksum, frozenSourceGeneratedDocumentIdentities, frozenSourceGeneratedDocuments); + return (compilationStateChecksums, projectCone); } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 3e6200977dde5..55a51fc200de6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -74,9 +74,12 @@ private SolutionState( _lazyAnalyzers = lazyAnalyzers ?? CreateLazyHostDiagnosticAnalyzers(analyzerReferences); // when solution state is changed, we recalculate its checksum - _lazyChecksums = AsyncLazy.Create(static (self, c) => - self.ComputeChecksumsAsync(projectConeId: null, c), - arg: this); + _lazyChecksums = AsyncLazy.Create(static async (self, cancellationToken) => + { + var (checksums, projectCone) = await self.ComputeChecksumsAsync(projectConeId: null, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue(projectCone != null); + return checksums; + }, arg: this); CheckInvariants(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 50e95e08fa857..384a43b98ce2e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -33,7 +33,7 @@ internal partial class SolutionState /// Mapping from project-id to the checksums needed to synchronize it (and the projects it depends on) over /// to an OOP host. Lock this specific field before reading/writing to it. /// - private readonly Dictionary> _lazyProjectChecksums = []; + private readonly Dictionary> _lazyProjectChecksums = []; public static IReadOnlyList GetOrCreateSortedProjectIds(IReadOnlyList unorderedList) => s_projectIdToSortedProjectsMap.GetValue(unorderedList, projectIds => projectIds.OrderBy(id => id.Id).ToImmutableArray()); @@ -43,18 +43,25 @@ public bool TryGetStateChecksums([NotNullWhen(true)] out SolutionStateChecksums? public bool TryGetStateChecksums(ProjectId projectId, [NotNullWhen(true)] out SolutionStateChecksums? stateChecksums) { - AsyncLazy? checksums; + AsyncLazy<(SolutionStateChecksums checksums, ProjectCone projectCone)>? lazyChecksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums) || - checksums == null) + if (!_lazyProjectChecksums.TryGetValue(projectId, out lazyChecksums) || + lazyChecksums == null) { stateChecksums = null; return false; } } - return checksums.TryGetValue(out stateChecksums); + if (lazyChecksums.TryGetValue(out var checksumsAndProjectCone)) + { + stateChecksums = null; + return false; + } + + stateChecksums = checksumsAndProjectCone.checksums; + return true; } public Task GetStateChecksumsAsync(CancellationToken cancellationToken) @@ -67,50 +74,51 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken } /// Gets the checksum for only the requested project (and any project it depends on) - public async Task GetStateChecksumsAsync( - ProjectId projectId, + public async Task<(SolutionStateChecksums stateChecksums, ProjectCone projectCone)> GetStateChecksumsAsync( + ProjectId projectConeId, CancellationToken cancellationToken) { - Contract.ThrowIfNull(projectId); + Contract.ThrowIfNull(projectConeId); - AsyncLazy? checksums; + AsyncLazy<(SolutionStateChecksums stateChecksums, ProjectCone projectCone)>? checksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums)) + if (!_lazyProjectChecksums.TryGetValue(projectConeId, out checksums)) { - checksums = Compute(projectId); - _lazyProjectChecksums.Add(projectId, checksums); + checksums = AsyncLazy.Create(static async (arg, cancellationToken) => + { + var (checksums, projectCone) = await arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(projectCone); + return (checksums, projectCone.Value); + }, arg: (self: this, projectConeId)); + _lazyProjectChecksums.Add(projectConeId, checksums); } } var collection = await checksums.GetValueAsync(cancellationToken).ConfigureAwait(false); return collection; - - // Extracted as a local function to prevent delegate allocations when not needed. - AsyncLazy Compute(ProjectId projectConeId) - { - return AsyncLazy.Create(static (arg, c) => - arg.self.ComputeChecksumsAsync(arg.projectConeId, c), - arg: (self: this, projectConeId)); - } } /// Gets the checksum for only the requested project (and any project it depends on) public async Task GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) { - var checksums = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + var (checksums, _) = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); return checksums.Checksum; } /// Cone of projects to compute a checksum for. Pass in to get a /// checksum for the entire solution - private async Task ComputeChecksumsAsync( + private async Task<(SolutionStateChecksums stateChecksums, ProjectCone? projectCone)> ComputeChecksumsAsync( ProjectId? projectConeId, CancellationToken cancellationToken) { - using var projectCone = SharedPools.Default>().GetPooledObject(); + var projectConeSet = projectConeId is null ? null : new HashSet(); AddProjectCone(projectConeId); + ProjectCone? projectCone = projectConeId is null + ? null + : new ProjectCone(projectConeId, SpecializedCollections.StronglyTypedReadOnlySet(projectConeSet!)); + try { using (Logger.LogBlock(FunctionId.SolutionState_ComputeChecksumsAsync, this.FilePath, cancellationToken)) @@ -127,7 +135,7 @@ private async Task ComputeChecksumsAsync( if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) continue; - if (projectConeId != null && !projectCone.Object.Contains(orderedProjectId)) + if (projectConeId != null && !projectConeSet!.Contains(orderedProjectId)) continue; projectChecksumTasks.Add(projectState.GetStateChecksumsAsync(cancellationToken)); @@ -141,11 +149,13 @@ private async Task ComputeChecksumsAsync( var analyzerReferenceChecksums = ChecksumCache.GetOrCreateChecksumCollection( this.AnalyzerReferences, this.Services.GetRequiredService(), cancellationToken); - return new SolutionStateChecksums( + var stateChecksums = new SolutionStateChecksums( projectConeId, this.SolutionAttributes.Checksum, new(new ChecksumCollection(projectChecksums), projectIds), analyzerReferenceChecksums); + + return (stateChecksums, projectCone); } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) @@ -155,10 +165,10 @@ private async Task ComputeChecksumsAsync( void AddProjectCone(ProjectId? projectConeId) { - if (projectConeId is null) + if (projectConeId is null || projectConeSet is null) return; - if (!projectCone.Object.Add(projectConeId)) + if (!projectConeSet.Add(projectConeId)) return; var projectState = this.GetProjectState(projectConeId); diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 301a0075ac622..2a4f6acc359c7 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -18,7 +18,7 @@ internal partial class SolutionAssetStorage internal sealed partial class Scope( SolutionAssetStorage storage, Checksum solutionChecksum, - ProjectId? projectId, + ProjectCone projectCone, SolutionCompilationState compilationState) : IDisposable { private readonly SolutionAssetStorage _storage = storage; @@ -69,16 +69,17 @@ private async Task FindAssetsAsync( AssetHint assetHint, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) { var solutionState = this.CompilationState; - SolutionCompilationStateChecksums? stateChecksums; if (ProjectId is null) - solutionState.TryGetStateChecksums(out stateChecksums); + { + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); + await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + } else - solutionState.TryGetStateChecksums(ProjectId, out stateChecksums); - - Contract.ThrowIfNull(stateChecksums); - - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + { + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)); + await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + } } public TestAccessor GetTestAccessor() From 7a83e996bd69e19c1b10c5e456802b8eec2d6b0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 11:49:15 -0700 Subject: [PATCH 017/170] Add more project cone info --- .../SolutionCompilationState_Checksum.cs | 6 +-- .../Solution/SolutionState_Checksum.cs | 8 ++-- .../Workspace/Solution/StateChecksums.cs | 40 ++++++++++++++----- .../Remote/Core/SolutionAssetStorage.Scope.cs | 15 ++++--- .../Remote/Core/SolutionAssetStorage.cs | 18 +++++++-- .../Remote/ServiceHub/Host/TestUtils.cs | 17 ++++---- 6 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index a730edce084aa..b7872fa8ee440 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -104,10 +104,10 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken } /// Gets the checksum for only the requested project (and any project it depends on) - public async Task<(Checksum checksum, ProjectCone projectCone)> GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) + public async Task GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) { - var (checksums, projectCone) = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - return (checksums.Checksum, projectCone); + var (checksums, _) = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + return checksums.Checksum; } private async Task<(SolutionCompilationStateChecksums checksums, ProjectCone? projectCone)> ComputeChecksumsAsync( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 384a43b98ce2e..fa278cd606deb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -33,7 +33,7 @@ internal partial class SolutionState /// Mapping from project-id to the checksums needed to synchronize it (and the projects it depends on) over /// to an OOP host. Lock this specific field before reading/writing to it. /// - private readonly Dictionary> _lazyProjectChecksums = []; + private readonly Dictionary> _lazyProjectChecksums = []; public static IReadOnlyList GetOrCreateSortedProjectIds(IReadOnlyList unorderedList) => s_projectIdToSortedProjectsMap.GetValue(unorderedList, projectIds => projectIds.OrderBy(id => id.Id).ToImmutableArray()); @@ -74,13 +74,13 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken } /// Gets the checksum for only the requested project (and any project it depends on) - public async Task<(SolutionStateChecksums stateChecksums, ProjectCone projectCone)> GetStateChecksumsAsync( + public async Task<(SolutionStateChecksums checksums, ProjectCone projectCone)> GetStateChecksumsAsync( ProjectId projectConeId, CancellationToken cancellationToken) { Contract.ThrowIfNull(projectConeId); - AsyncLazy<(SolutionStateChecksums stateChecksums, ProjectCone projectCone)>? checksums; + AsyncLazy<(SolutionStateChecksums checksums, ProjectCone projectCone)>? checksums; lock (_lazyProjectChecksums) { if (!_lazyProjectChecksums.TryGetValue(projectConeId, out checksums)) @@ -108,7 +108,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo /// Cone of projects to compute a checksum for. Pass in to get a /// checksum for the entire solution - private async Task<(SolutionStateChecksums stateChecksums, ProjectCone? projectCone)> ComputeChecksumsAsync( + private async Task<(SolutionStateChecksums checksums, ProjectCone? projectCone)> ComputeChecksumsAsync( ProjectId? projectConeId, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index a503a170120ac..106e89044e6f1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -88,6 +88,7 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) public async Task FindAsync( SolutionCompilationState compilationState, + ProjectCone? projectCone, AssetHint assetHint, HashSet searchingChecksumsLeft, Dictionary result, @@ -123,16 +124,18 @@ public async Task FindAsync( } var solutionState = compilationState.SolutionState; - if (solutionState.TryGetStateChecksums(out var solutionChecksums)) - await solutionChecksums.FindAsync(solutionState, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - - foreach (var projectId in solutionState.ProjectIds) + if (projectCone is null) { - if (searchingChecksumsLeft.Count == 0) - break; - - if (solutionState.TryGetStateChecksums(projectId, out solutionChecksums)) - await solutionChecksums.FindAsync(solutionState, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + // If we're not in a project cone, start the search at the top most state-checksum corresponding to the + // entire solution. + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var solutionChecksums)); + await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + } + else + { + // Otherwise, grab the top-most state checksum for this cone and search within that. + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(projectCone.Value.RootProjectId, out var solutionChecksums)); + await solutionChecksums.FindAsync(solutionState, projectCone, assetHint, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } @@ -193,6 +196,7 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) public async Task FindAsync( SolutionState solution, + ProjectCone? projectCone, AssetHint assetHint, HashSet searchingChecksumsLeft, Dictionary result, @@ -216,6 +220,10 @@ public async Task FindAsync( if (assetHint.ProjectId != null) { + Contract.ThrowIfTrue( + projectCone != null && !projectCone.Value.ProjectIds.Contains(assetHint.ProjectId), + "Requesting an asset outside of the cone explicitly being asked for!"); + var projectState = solution.GetProjectState(assetHint.ProjectId); if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) @@ -231,11 +239,16 @@ public async Task FindAsync( // level. This ensures that when we are trying to sync the projects referenced by a SolutionStateChecksums' // instance that we don't unnecessarily walk all documents looking just for those. - foreach (var (_, projectState) in solution.ProjectStates) + foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) break; + // If we're syncing a project cone, no point at all at looking at child projects of the solution that + // are not in that cone. + if (projectCone != null && !projectCone.Value.ProjectIds.Contains(projectId)) + continue; + if (projectState.TryGetStateChecksums(out var projectStateChecksums) && searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) { @@ -245,11 +258,16 @@ public async Task FindAsync( // Now actually do the depth first search into each project. - foreach (var (_, projectState) in solution.ProjectStates) + foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) break; + // If we're syncing a project cone, no point at all at looking at child projects of the solution that + // are not in that cone. + if (projectCone != null && !projectCone.Value.ProjectIds.Contains(projectId)) + continue; + // It's possible not all all our projects have checksums. Specifically, we may have only been asked to // compute the checksum tree for a subset of projects that were all that a feature needed. if (projectState.TryGetStateChecksums(out var projectStateChecksums)) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 2a4f6acc359c7..d8b59a0670f12 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -18,13 +18,13 @@ internal partial class SolutionAssetStorage internal sealed partial class Scope( SolutionAssetStorage storage, Checksum solutionChecksum, - ProjectCone projectCone, + ProjectCone? projectCone, SolutionCompilationState compilationState) : IDisposable { private readonly SolutionAssetStorage _storage = storage; public readonly Checksum SolutionChecksum = solutionChecksum; - public readonly ProjectId? ProjectId = projectId; + public readonly ProjectCone? ProjectCone = projectCone; public readonly SolutionCompilationState CompilationState = compilationState; /// @@ -70,15 +70,18 @@ private async Task FindAssetsAsync( { var solutionState = this.CompilationState; - if (ProjectId is null) + if (this.ProjectCone is null) { + // If we're not in a project cone, start the search at the top most state-checksum corresponding to the + // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } else { - Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(ProjectId, out var stateChecksums)); - await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + // Otherwise, grab the top-most state checksum for this cone and search within that. + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(this.ProjectCone.Value.RootProjectId, out var stateChecksums)); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 0287a3dee74e2..841b07901f7a8 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -63,9 +63,19 @@ public ValueTask StoreAssetsAsync(SolutionCompilationState compilationSta /// public async ValueTask StoreAssetsAsync(SolutionCompilationState compilationState, ProjectId? projectId, CancellationToken cancellationToken) { - var checksum = projectId == null - ? await compilationState.GetChecksumAsync(cancellationToken).ConfigureAwait(false) - : await compilationState.GetChecksumAsync(projectId, cancellationToken).ConfigureAwait(false); + Checksum checksum; + ProjectCone? projectCone; + + if (projectId == null) + { + checksum = await compilationState.GetChecksumAsync(cancellationToken).ConfigureAwait(false); + projectCone = null; + } + else + { + (var stateChecksums, projectCone) = await compilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + checksum = stateChecksums.Checksum; + } lock (_gate) { @@ -76,7 +86,7 @@ public async ValueTask StoreAssetsAsync(SolutionCompilationState compilat return scope; } - scope = new Scope(this, checksum, projectId, compilationState); + scope = new Scope(this, checksum, projectCone, compilationState); _checksumToScope[checksum] = scope; return scope; } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 4e84b56321074..b124eceb4bfba 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -189,35 +189,36 @@ public static async Task> GetAssetMapAsync(this Pro } public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, CancellationToken cancellationToken) - => AppendAssetMapAsync(solution, map, projectId: null, cancellationToken); + => AppendAssetMapAsync(solution, projectCone: null, map, cancellationToken); public static async Task AppendAssetMapAsync( - this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) + this Solution solution, ProjectCone? projectCone, Dictionary map, CancellationToken cancellationToken) { - if (projectId == null) + if (projectCone == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var frozenSourceGeneratedDocumentState in solution.CompilationState.FrozenSourceGeneratedDocumentStates?.States.Values ?? []) { var documentChecksums = await frozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); } var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); } else { + var projectId = projectCone.Value.RootProjectId; var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, assetHint: projectId, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.checksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums.checksums), map, cancellationToken).ConfigureAwait(false); var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.checksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums.checksums), map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); From 42752cf93286b4c21bdbdebfc86b6d444f4744f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 11:58:04 -0700 Subject: [PATCH 018/170] undo --- .../SolutionCompilationState_Checksum.cs | 19 +++++++++++-------- .../Remote/ServiceHub/Host/TestUtils.cs | 18 +++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index b7872fa8ee440..e14a944deac40 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -14,16 +14,19 @@ namespace Microsoft.CodeAnalysis; -internal readonly struct ProjectCone +internal readonly struct ProjectCone(ProjectId rootProjectId, IReadOnlySet projectIds) : IEquatable { - public readonly ProjectId RootProjectId; - public readonly IReadOnlySet ProjectIds; + public readonly ProjectId RootProjectId = rootProjectId; + public readonly IReadOnlySet ProjectIds = projectIds; - public ProjectCone(ProjectId rootProjectId, IReadOnlySet projectIds) - { - RootProjectId = rootProjectId; - ProjectIds = projectIds; - } + public override bool Equals(object obj) + => obj is ProjectCone cone && Equals(cone); + + public bool Equals(ProjectCone other) + => this.RootProjectId == other.RootProjectId && this.ProjectIds.SetEquals(other.ProjectIds); + + public override int GetHashCode() + => throw new NotImplementedException(); } internal partial class SolutionCompilationState diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index b124eceb4bfba..713e1bd9b854c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -189,35 +189,35 @@ public static async Task> GetAssetMapAsync(this Pro } public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, CancellationToken cancellationToken) - => AppendAssetMapAsync(solution, projectCone: null, map, cancellationToken); + => AppendAssetMapAsync(solution, map, projectId: null, cancellationToken); public static async Task AppendAssetMapAsync( - this Solution solution, ProjectCone? projectCone, Dictionary map, CancellationToken cancellationToken) + this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { - if (projectCone == null) + if (projectId == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var frozenSourceGeneratedDocumentState in solution.CompilationState.FrozenSourceGeneratedDocumentStates?.States.Values ?? []) { var documentChecksums = await frozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, assetHint: AssetHint.None, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); } var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); } else { - var projectId = projectCone.Value.RootProjectId; - var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.checksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums.checksums), map, cancellationToken).ConfigureAwait(false); + var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(projectCone.Equals(solutionChecksums.projectCone)); await solutionChecksums.checksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums.checksums), map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); From 643cd2f5153024970799053737312b220582bd3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:15:30 -0700 Subject: [PATCH 019/170] fix --- .../Workspace/Solution/SolutionCompilationState_Checksum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index e14a944deac40..35a771497900d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -60,7 +60,7 @@ public bool TryGetStateChecksums(ProjectId projectId, [NotNullWhen(true)] out So } } - if (lazyChecksums.TryGetValue(out var checksumsAndCone)) + if (!lazyChecksums.TryGetValue(out var checksumsAndCone)) { stateChecksums = null; return false; From 931f7848bc8be63f68fac5bbd89da32baa4528ea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:16:18 -0700 Subject: [PATCH 020/170] fix --- .../Core/Portable/Workspace/Solution/SolutionState_Checksum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index fa278cd606deb..700bfffe51db0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -54,7 +54,7 @@ public bool TryGetStateChecksums(ProjectId projectId, [NotNullWhen(true)] out So } } - if (lazyChecksums.TryGetValue(out var checksumsAndProjectCone)) + if (!lazyChecksums.TryGetValue(out var checksumsAndProjectCone)) { stateChecksums = null; return false; From b10a170cebe53af40297ac7bf4b75176ee89cdf5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:25:51 -0700 Subject: [PATCH 021/170] Simplify --- .../Core/Portable/Workspace/Solution/SolutionState_Checksum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 700bfffe51db0..ec3a957621f19 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -135,7 +135,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) continue; - if (projectConeId != null && !projectConeSet!.Contains(orderedProjectId)) + if (projectConeSet != null && !projectConeSet.Contains(orderedProjectId)) continue; projectChecksumTasks.Add(projectState.GetStateChecksumsAsync(cancellationToken)); From 22521fca089899432d55cda8b2a7890fb6202bbd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:49:09 -0700 Subject: [PATCH 022/170] Extract file --- .../Workspace/Solution/ProjectCone.cs | 35 +++++++++++++++++++ .../SolutionCompilationState_Checksum.cs | 15 -------- 2 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs new file mode 100644 index 0000000000000..a3ee4dfe6976d --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs @@ -0,0 +1,35 @@ +// 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.Frozen; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Represents a 'cone' of projects that is being sync'ed between the local and remote hosts. A project cone starts +/// with a , and contains both it and all dependent projects within . +/// +internal sealed class ProjectCone : IEquatable +{ + public readonly ProjectId RootProjectId; + public readonly FrozenSet ProjectIds; + + public ProjectCone(ProjectId rootProjectId, FrozenSet projectIds) + { + Contract.ThrowIfFalse(projectIds.Contains(rootProjectId)); + RootProjectId = rootProjectId; + ProjectIds = projectIds; + } + + public override bool Equals(object? obj) + => obj is ProjectCone cone && Equals(cone); + + public bool Equals(ProjectCone? other) + => other is not null && this.RootProjectId == other.RootProjectId && this.ProjectIds.SetEquals(other.ProjectIds); + + public override int GetHashCode() + => throw new NotImplementedException(); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 8f61614448423..79d8e7ec11b65 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -15,21 +15,6 @@ namespace Microsoft.CodeAnalysis; -internal sealed class ProjectCone(ProjectId rootProjectId, FrozenSet projectIds) : IEquatable -{ - public readonly ProjectId RootProjectId = rootProjectId; - public readonly FrozenSet ProjectIds = projectIds; - - public override bool Equals(object? obj) - => obj is ProjectCone cone && Equals(cone); - - public bool Equals(ProjectCone? other) - => other is not null && this.RootProjectId == other.RootProjectId && this.ProjectIds.SetEquals(other.ProjectIds); - - public override int GetHashCode() - => throw new NotImplementedException(); -} - internal partial class SolutionCompilationState { /// From 2f82a01fe58459e900e95b18432f1051c6259560 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:51:52 -0700 Subject: [PATCH 023/170] Add assert --- .../Workspace/Solution/SolutionState_Checksum.cs | 16 ++++++++++++---- .../Remote/Core/SolutionAssetStorage.Scope.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 9af209a225472..4173fb80f2bf1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; @@ -102,7 +103,7 @@ private async Task ComputeChecksumsAsync( ProjectId? projectConeId, CancellationToken cancellationToken) { - using var _1 = PooledHashSet.GetInstance(out var projectCone); + using var _1 = PooledHashSet.GetInstance(out var projectConeSet); AddProjectCone(projectConeId); try @@ -121,7 +122,7 @@ private async Task ComputeChecksumsAsync( if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) continue; - if (projectConeId != null && !projectCone.Contains(orderedProjectId)) + if (projectConeId != null && !projectConeSet.Contains(orderedProjectId)) continue; projectChecksumTasks.Add(projectState.GetStateChecksumsAsync(cancellationToken)); @@ -135,11 +136,18 @@ private async Task ComputeChecksumsAsync( var analyzerReferenceChecksums = ChecksumCache.GetOrCreateChecksumCollection( this.AnalyzerReferences, this.Services.GetRequiredService(), cancellationToken); - return new SolutionStateChecksums( + var stateChecksums = new SolutionStateChecksums( projectConeId, this.SolutionAttributes.Checksum, new(new ChecksumCollection(projectChecksums), projectIds), analyzerReferenceChecksums); + +#if DEBUG + var projectCone = projectConeId is null ? null : new ProjectCone(projectConeId, projectConeSet.ToFrozenSet()); + RoslynDebug.Assert(Equals(projectCone, stateChecksums.ProjectCone)); +#endif + + return stateChecksums; } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) @@ -152,7 +160,7 @@ void AddProjectCone(ProjectId? projectConeId) if (projectConeId is null) return; - if (!projectCone.Add(projectConeId)) + if (!projectConeSet.Add(projectConeId)) return; var projectState = this.GetProjectState(projectConeId); diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index d8b59a0670f12..a87a15de4cfcb 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -80,7 +80,7 @@ private async Task FindAssetsAsync( else { // Otherwise, grab the top-most state checksum for this cone and search within that. - Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(this.ProjectCone.Value.RootProjectId, out var stateChecksums)); + Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(this.ProjectCone.RootProjectId, out var stateChecksums)); await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); } } From 5ac9b68cdf206f177e14e6eb1d95f2225d811515 Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Tue, 12 Mar 2024 12:52:18 -0700 Subject: [PATCH 024/170] Apply PR feedback - Rename option to be `RazorSourceGenerator` - Cache results in analyzer provider, since result is fixed per roslyn instance - Remove duped names taken out of ProjectSystemProject --- .../LanguageServerTestComposition.cs | 2 +- .../HostDiagnosticAnalyzerProvider.cs | 46 ++++++++----------- .../LanguageServerWorkspaceFactory.cs | 4 +- .../Program.cs | 10 ++-- .../ServerConfigurationFactory.cs | 2 +- .../ProjectSystem/ProjectSystemProject.cs | 2 +- 6 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs index 3422dc1c30a16..fab6370f9f30d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs @@ -32,7 +32,7 @@ public static Task CreateExportProviderAsync(ILoggerFactory logg SessionId: null, ExtensionAssemblyPaths: [], DevKitDependencyPath: devKitDependencyPath, - DevKitRazorOutputPath: null, + RazorSourceGenerator: null, ExtensionLogDirectory: string.Empty); var extensionAssemblyManager = ExtensionAssemblyManager.Create(serverConfiguration, loggerFactory); return ExportProviderBuilder.CreateExportProviderAsync(extensionAssemblyManager, devKitDependencyPath, loggerFactory: loggerFactory); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs index 517275e99ad9a..dabf29f8ba48e 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs @@ -12,48 +12,40 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; internal class HostDiagnosticAnalyzerProvider : IHostDiagnosticAnalyzerProvider { /// - /// The directory where the analyzers are located + /// The full path to the Razor source generator /// - public string? AnalyzerDirectory { get; set; } + public string? RazorSourceGenerator { get; set; } - private const string RazorVsixExtensionId = "Microsoft.VisualStudio.RazorExtension"; - private static readonly HashSet s_razorSourceGeneratorAssemblyNames = new[] { - "Microsoft.NET.Sdk.Razor.SourceGenerators", - "Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators", - "Microsoft.CodeAnalysis.Razor.Compiler", - }.ToHashSet(); + private ImmutableArray<(AnalyzerFileReference reference, string extensionId)>? _cachedAnalyzerReferences; - public HostDiagnosticAnalyzerProvider(string? analyzerDirectory) + public HostDiagnosticAnalyzerProvider(string? razorSourceGenerator) { - AnalyzerDirectory = analyzerDirectory; + RazorSourceGenerator = razorSourceGenerator; } public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() { - if (AnalyzerDirectory == null) + if (_cachedAnalyzerReferences != null) { - return ImmutableArray<(AnalyzerFileReference reference, string extensionId)>.Empty; + return _cachedAnalyzerReferences.Value; } - var analyzerReferences = new List<(AnalyzerFileReference, string)>(); + if (RazorSourceGenerator == null) + { + _cachedAnalyzerReferences = ImmutableArray<(AnalyzerFileReference reference, string extensionId)>.Empty; + return _cachedAnalyzerReferences.Value; + } - // Get all the .dll files in the directory - var analyzerFiles = Directory.GetFiles(AnalyzerDirectory, "*.dll"); + var analyzerReferences = new List<(AnalyzerFileReference, string)>(); - foreach (var analyzerFile in analyzerFiles) - { - // Create an AnalyzerFileReference for each file - var analyzerReference = new AnalyzerFileReference(analyzerFile, new SimpleAnalyzerAssemblyLoader()); + // Create an AnalyzerFileReference for each file + var analyzerReference = new AnalyzerFileReference(RazorSourceGenerator, new SimpleAnalyzerAssemblyLoader()); - if (s_razorSourceGeneratorAssemblyNames.Any( - name => analyzerReference.FullPath.Contains(name, StringComparison.OrdinalIgnoreCase))) - { - // Add the reference to the list, using the file name as the extension ID - analyzerReferences.Add((analyzerReference, RazorVsixExtensionId)); - } - } + // Add the reference to the list, using the file name as the extension ID + analyzerReferences.Add((analyzerReference, ProjectSystemProject.RazorVsixExtensionId)); - return analyzerReferences.ToImmutableArray(); + _cachedAnalyzerReferences = analyzerReferences.ToImmutableArray(); + return _cachedAnalyzerReferences.Value; } private class SimpleAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs index 82055c4c58a46..3936424280067 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs @@ -41,11 +41,11 @@ public LanguageServerWorkspaceFactory( analyzerLoader.InitializeDiagnosticsServices(Workspace); - var devKitRazorOutputPath = serverConfigurationFactory?.ServerConfiguration?.DevKitRazorOutputPath; + var razorSourceGenerator = serverConfigurationFactory?.ServerConfiguration?.RazorSourceGenerator; ProjectSystemHostInfo = new ProjectSystemHostInfo( DynamicFileInfoProviders: dynamicFileInfoProviders.ToImmutableArray(), new ProjectSystemDiagnosticSource(), - new HostDiagnosticAnalyzerProvider(devKitRazorOutputPath)); + new HostDiagnosticAnalyzerProvider(razorSourceGenerator)); TargetFrameworkManager = projectTargetFrameworkManager; } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index ebd2790dc7441..56a0a041edd55 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -198,9 +198,9 @@ static CliRootCommand CreateCommandLineParser() Required = false }; - var devKitRazorOutputPathOption = new CliOption("--devKitRazorOutputPath") + var razorSourceGeneratorOption = new CliOption("--razorSourceGenerator") { - Description = "Full path to the Razor output path used with DevKit (optional).", + Description = "Full path to the Razor source generator (optional).", Required = false }; @@ -214,7 +214,7 @@ static CliRootCommand CreateCommandLineParser() sessionIdOption, extensionAssemblyPathsOption, devKitDependencyPathOption, - devKitRazorOutputPathOption, + razorSourceGeneratorOption, extensionLogDirectoryOption }; rootCommand.SetAction((parseResult, cancellationToken) => @@ -226,7 +226,7 @@ static CliRootCommand CreateCommandLineParser() var sessionId = parseResult.GetValue(sessionIdOption); var extensionAssemblyPaths = parseResult.GetValue(extensionAssemblyPathsOption) ?? []; var devKitDependencyPath = parseResult.GetValue(devKitDependencyPathOption); - var devKitRazorOutputPath = parseResult.GetValue(devKitRazorOutputPathOption); + var razorSourceGenerator = parseResult.GetValue(razorSourceGeneratorOption); var extensionLogDirectory = parseResult.GetValue(extensionLogDirectoryOption)!; var serverConfiguration = new ServerConfiguration( @@ -237,7 +237,7 @@ static CliRootCommand CreateCommandLineParser() SessionId: sessionId, ExtensionAssemblyPaths: extensionAssemblyPaths, DevKitDependencyPath: devKitDependencyPath, - DevKitRazorOutputPath: devKitRazorOutputPath, + RazorSourceGenerator: razorSourceGenerator, ExtensionLogDirectory: extensionLogDirectory); return RunAsync(serverConfiguration, cancellationToken); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ServerConfigurationFactory.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ServerConfigurationFactory.cs index ce7496dd8d9f7..f95acef7d3f83 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ServerConfigurationFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ServerConfigurationFactory.cs @@ -49,5 +49,5 @@ internal record class ServerConfiguration( string? SessionId, IEnumerable ExtensionAssemblyPaths, string? DevKitDependencyPath, - string? DevKitRazorOutputPath, + string? RazorSourceGenerator, string ExtensionLogDirectory); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 9e900ceeaf494..fd6599b2c1565 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -989,7 +989,7 @@ public void RemoveAnalyzerReference(string fullPath) } } - private const string RazorVsixExtensionId = "Microsoft.VisualStudio.RazorExtension"; + internal const string RazorVsixExtensionId = "Microsoft.VisualStudio.RazorExtension"; private static readonly string s_razorSourceGeneratorSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk.Razor", "source-generators") + PathUtilities.DirectorySeparatorStr; private static readonly ImmutableArray s_razorSourceGeneratorAssemblyNames = [ From 32904a752ecc532f89e87faf7e11bd49469153dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:55:07 -0700 Subject: [PATCH 025/170] Add assert --- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 713e1bd9b854c..b5904468664f6 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -217,8 +217,8 @@ public static async Task AppendAssetMapAsync( await compilationChecksums.FindAsync(solution.CompilationState, projectCone, assetHint: projectId, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfFalse(projectCone.Equals(solutionChecksums.projectCone)); - await solutionChecksums.checksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums.checksums), map, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfFalse(projectCone.Equals(solutionChecksums.ProjectCone)); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); From 79d4a49c1cb2f59e28148ed07625dc5585573c89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:55:42 -0700 Subject: [PATCH 026/170] Add assert --- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index b5904468664f6..e942c5e44f5fd 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -206,6 +206,7 @@ public static async Task AppendAssetMapAsync( } var solutionChecksums = await solution.CompilationState.SolutionState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfTrue(solutionChecksums.ProjectCone != null); await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) From 9c8996b2613664c049ae5a70cba3986ab06688a6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:57:56 -0700 Subject: [PATCH 027/170] Revert --- .../Core/Portable/Workspace/Solution/SolutionState.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 2dbf12c4c1259..3e6200977dde5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -74,8 +74,8 @@ private SolutionState( _lazyAnalyzers = lazyAnalyzers ?? CreateLazyHostDiagnosticAnalyzers(analyzerReferences); // when solution state is changed, we recalculate its checksum - _lazyChecksums = AsyncLazy.Create( - static (self, cancellationToken) => self.ComputeChecksumsAsync(projectConeId: null, cancellationToken), + _lazyChecksums = AsyncLazy.Create(static (self, c) => + self.ComputeChecksumsAsync(projectConeId: null, c), arg: this); CheckInvariants(); From 417449896590a7b79347a9a740b26abf06906f56 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:58:32 -0700 Subject: [PATCH 028/170] Revert --- .../Portable/Workspace/Solution/SolutionState_Checksum.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 4173fb80f2bf1..08594d36138f5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -44,18 +44,18 @@ public bool TryGetStateChecksums([NotNullWhen(true)] out SolutionStateChecksums? public bool TryGetStateChecksums(ProjectId projectId, [NotNullWhen(true)] out SolutionStateChecksums? stateChecksums) { - AsyncLazy? lazyChecksums; + AsyncLazy? checksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectId, out lazyChecksums) || - lazyChecksums == null) + if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums) || + checksums == null) { stateChecksums = null; return false; } } - return lazyChecksums.TryGetValue(out stateChecksums); + return checksums.TryGetValue(out stateChecksums); } public Task GetStateChecksumsAsync(CancellationToken cancellationToken) From 60810f36d97745e77cd6e156c4373960662a45f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:58:51 -0700 Subject: [PATCH 029/170] Revert --- .../Portable/Workspace/Solution/SolutionState_Checksum.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 08594d36138f5..5ca0e8621deba 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -93,8 +93,8 @@ public async Task GetStateChecksumsAsync( /// Gets the checksum for only the requested project (and any project it depends on) public async Task GetChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) { - var stateChecksums = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - return stateChecksums.Checksum; + var checksums = await GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + return checksums.Checksum; } /// Cone of projects to compute a checksum for. Pass in to get a From 615b6e91c96704a15c2c9d7aefdd97c4a97143c5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 12:59:35 -0700 Subject: [PATCH 030/170] Revert --- .../Workspace/Solution/SolutionState_Checksum.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 5ca0e8621deba..da9625f54a0c1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -103,7 +103,7 @@ private async Task ComputeChecksumsAsync( ProjectId? projectConeId, CancellationToken cancellationToken) { - using var _1 = PooledHashSet.GetInstance(out var projectConeSet); + using var projectConeSet = SharedPools.Default>().GetPooledObject(); AddProjectCone(projectConeId); try @@ -114,7 +114,7 @@ private async Task ComputeChecksumsAsync( // requested set of projects if applicable. var orderedProjectIds = GetOrCreateSortedProjectIds(this.ProjectIds); - using var _2 = ArrayBuilder>.GetInstance(out var projectChecksumTasks); + using var _ = ArrayBuilder>.GetInstance(out var projectChecksumTasks); foreach (var orderedProjectId in orderedProjectIds) { @@ -122,7 +122,7 @@ private async Task ComputeChecksumsAsync( if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) continue; - if (projectConeId != null && !projectConeSet.Contains(orderedProjectId)) + if (projectConeId != null && !projectConeSet.Object.Contains(orderedProjectId)) continue; projectChecksumTasks.Add(projectState.GetStateChecksumsAsync(cancellationToken)); @@ -143,7 +143,7 @@ private async Task ComputeChecksumsAsync( analyzerReferenceChecksums); #if DEBUG - var projectCone = projectConeId is null ? null : new ProjectCone(projectConeId, projectConeSet.ToFrozenSet()); + var projectCone = projectConeId is null ? null : new ProjectCone(projectConeId, projectConeSet.Object.ToFrozenSet()); RoslynDebug.Assert(Equals(projectCone, stateChecksums.ProjectCone)); #endif @@ -160,7 +160,7 @@ void AddProjectCone(ProjectId? projectConeId) if (projectConeId is null) return; - if (!projectConeSet.Add(projectConeId)) + if (!projectConeSet.Object.Add(projectConeId)) return; var projectState = this.GetProjectState(projectConeId); From 0aa9398ea5ca52df11865ee447284c9ce4b2dc06 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:01:10 -0700 Subject: [PATCH 031/170] Revert --- .../Solution/SolutionCompilationState_Checksum.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 79d8e7ec11b65..ac99345a0b998 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -67,24 +67,24 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken /// Gets the checksum for only the requested project (and any project it depends on) public async Task<(SolutionCompilationStateChecksums checksums, ProjectCone projectCone)> GetStateChecksumsAsync( - ProjectId projectConeId, + ProjectId projectId, CancellationToken cancellationToken) { - Contract.ThrowIfNull(projectConeId); + Contract.ThrowIfNull(projectId); AsyncLazy<(SolutionCompilationStateChecksums checksums, ProjectCone projectCone)>? checksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectConeId, out checksums)) + if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums)) { checksums = AsyncLazy.Create(static async (arg, cancellationToken) => { var (checksum, projectCone) = await arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(projectCone); return (checksum, projectCone); - }, arg: (self: this, projectConeId)); + }, arg: (self: this, projectId)); - _lazyProjectChecksums.Add(projectConeId, checksums); + _lazyProjectChecksums.Add(projectId, checksums); } } From 7dc12fa46f6e3305b0e47b689910c12b85680609 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:01:54 -0700 Subject: [PATCH 032/170] Revert --- .../Workspace/Solution/SolutionState_Checksum.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index da9625f54a0c1..bddb8685dc33f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -69,20 +69,20 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken /// Gets the checksum for only the requested project (and any project it depends on) public async Task GetStateChecksumsAsync( - ProjectId projectConeId, + ProjectId projectId, CancellationToken cancellationToken) { - Contract.ThrowIfNull(projectConeId); + Contract.ThrowIfNull(projectId); AsyncLazy? checksums; lock (_lazyProjectChecksums) { - if (!_lazyProjectChecksums.TryGetValue(projectConeId, out checksums)) + if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums)) { checksums = AsyncLazy.Create( static (arg, cancellationToken) => arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken), - arg: (self: this, projectConeId)); - _lazyProjectChecksums.Add(projectConeId, checksums); + arg: (self: this, projectId)); + _lazyProjectChecksums.Add(projectId, checksums); } } From 15e75cf5ed063311ea3c463bef9b6a969eeceae6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:02:40 -0700 Subject: [PATCH 033/170] Revert --- .../Workspace/Solution/SolutionState_Checksum.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index bddb8685dc33f..c2643951f6382 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -103,7 +103,7 @@ private async Task ComputeChecksumsAsync( ProjectId? projectConeId, CancellationToken cancellationToken) { - using var projectConeSet = SharedPools.Default>().GetPooledObject(); + using var projectCone = SharedPools.Default>().GetPooledObject(); AddProjectCone(projectConeId); try @@ -122,7 +122,7 @@ private async Task ComputeChecksumsAsync( if (!RemoteSupportedLanguages.IsSupported(projectState.Language)) continue; - if (projectConeId != null && !projectConeSet.Object.Contains(orderedProjectId)) + if (projectConeId != null && !projectCone.Object.Contains(orderedProjectId)) continue; projectChecksumTasks.Add(projectState.GetStateChecksumsAsync(cancellationToken)); @@ -143,8 +143,8 @@ private async Task ComputeChecksumsAsync( analyzerReferenceChecksums); #if DEBUG - var projectCone = projectConeId is null ? null : new ProjectCone(projectConeId, projectConeSet.Object.ToFrozenSet()); - RoslynDebug.Assert(Equals(projectCone, stateChecksums.ProjectCone)); + var projectConeTemp = projectConeId is null ? null : new ProjectCone(projectConeId, projectCone.Object.ToFrozenSet()); + RoslynDebug.Assert(Equals(projectConeTemp, stateChecksums.ProjectCone)); #endif return stateChecksums; @@ -160,7 +160,7 @@ void AddProjectCone(ProjectId? projectConeId) if (projectConeId is null) return; - if (!projectConeSet.Object.Add(projectConeId)) + if (!projectCone.Object.Add(projectConeId)) return; var projectState = this.GetProjectState(projectConeId); From 175874f2fe9dac3038821d403f964365c507a6d4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:03:00 -0700 Subject: [PATCH 034/170] Revert --- .../Workspace/Solution/SolutionCompilationState_Checksum.cs | 2 +- .../Core/Portable/Workspace/Solution/SolutionState_Checksum.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index ac99345a0b998..e3e503ce3836b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -79,7 +79,7 @@ public async Task GetChecksumAsync(CancellationToken cancellationToken { checksums = AsyncLazy.Create(static async (arg, cancellationToken) => { - var (checksum, projectCone) = await arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken).ConfigureAwait(false); + var (checksum, projectCone) = await arg.self.ComputeChecksumsAsync(arg.projectId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(projectCone); return (checksum, projectCone); }, arg: (self: this, projectId)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index c2643951f6382..d6d21282fd084 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -80,7 +80,7 @@ public async Task GetStateChecksumsAsync( if (!_lazyProjectChecksums.TryGetValue(projectId, out checksums)) { checksums = AsyncLazy.Create( - static (arg, cancellationToken) => arg.self.ComputeChecksumsAsync(arg.projectConeId, cancellationToken), + static (arg, cancellationToken) => arg.self.ComputeChecksumsAsync(arg.projectId, cancellationToken), arg: (self: this, projectId)); _lazyProjectChecksums.Add(projectId, checksums); } From e63278f5ff4b2f11636f24c32401829ca9f00bf3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:04:45 -0700 Subject: [PATCH 035/170] Comment --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 605d875812de2..e40c1fa3753b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -164,6 +164,9 @@ internal sealed class SolutionStateChecksums( public ChecksumsAndIds Projects { get; } = projects; public ChecksumCollection AnalyzerReferences { get; } = analyzerReferences; + // Acceptably not threadsafe. ProjectCone is a class, and the runtime guarantees anyone will see this field fully + // initialized. It's acceptable to have multiple instances of this in a race condition as the data will be same + // (and our asserts don't check for reference equality, only value equality). public ProjectCone? ProjectCone => _projectCone ??= ComputeProjectCone(); private ProjectCone? ComputeProjectCone() From 5bb72183ec479edc39fc3689fac98b2e4645a8f6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:06:21 -0700 Subject: [PATCH 036/170] Simplify --- .../Core/Portable/Workspace/Solution/ProjectCone.cs | 11 +++++++---- .../Portable/Workspace/Solution/StateChecksums.cs | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs index a3ee4dfe6976d..8986e7b36129a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectCone.cs @@ -10,25 +10,28 @@ namespace Microsoft.CodeAnalysis; /// /// Represents a 'cone' of projects that is being sync'ed between the local and remote hosts. A project cone starts -/// with a , and contains both it and all dependent projects within . +/// with a , and contains both it and all dependent projects within . /// internal sealed class ProjectCone : IEquatable { public readonly ProjectId RootProjectId; - public readonly FrozenSet ProjectIds; + private readonly FrozenSet _projectIds; public ProjectCone(ProjectId rootProjectId, FrozenSet projectIds) { Contract.ThrowIfFalse(projectIds.Contains(rootProjectId)); RootProjectId = rootProjectId; - ProjectIds = projectIds; + _projectIds = projectIds; } + public bool Contains(ProjectId projectId) + => _projectIds.Contains(projectId); + public override bool Equals(object? obj) => obj is ProjectCone cone && Equals(cone); public bool Equals(ProjectCone? other) - => other is not null && this.RootProjectId == other.RootProjectId && this.ProjectIds.SetEquals(other.ProjectIds); + => other is not null && this.RootProjectId == other.RootProjectId && this._projectIds.SetEquals(other._projectIds); public override int GetHashCode() => throw new NotImplementedException(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index e40c1fa3753b0..e030b033f7275 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -232,7 +232,7 @@ public async Task FindAsync( if (assetHint.ProjectId != null) { Contract.ThrowIfTrue( - projectCone != null && !projectCone.ProjectIds.Contains(assetHint.ProjectId), + projectCone != null && !projectCone.Contains(assetHint.ProjectId), "Requesting an asset outside of the cone explicitly being asked for!"); var projectState = solution.GetProjectState(assetHint.ProjectId); @@ -257,7 +257,7 @@ public async Task FindAsync( // If we're syncing a project cone, no point at all at looking at child projects of the solution that // are not in that cone. - if (projectCone != null && !projectCone.ProjectIds.Contains(projectId)) + if (projectCone != null && !projectCone.Contains(projectId)) continue; if (projectState.TryGetStateChecksums(out var projectStateChecksums) && @@ -276,7 +276,7 @@ public async Task FindAsync( // If we're syncing a project cone, no point at all at looking at child projects of the solution that // are not in that cone. - if (projectCone != null && !projectCone.ProjectIds.Contains(projectId)) + if (projectCone != null && !projectCone.Contains(projectId)) continue; // It's possible not all all our projects have checksums. Specifically, we may have only been asked to From 1a6275121640cc9d417c9c13ac5dcd9d5b3cf978 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 13:15:32 -0700 Subject: [PATCH 037/170] REmove --- .../Workspace/Solution/SolutionCompilationState_Checksum.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index e3e503ce3836b..830d52cd72c3d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; From 8e506122b1c431509cbe990e6f2156ec6a5cdc6a Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Tue, 12 Mar 2024 16:33:28 -0400 Subject: [PATCH 038/170] Add a unit test to ensure object initialisers supports completion in collection expressions --- ...bjectInitializerCompletionProviderTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs index 227666c5027ff..f30844e7267b3 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs @@ -185,6 +185,43 @@ void goo() await VerifyExclusiveAsync(markup, true); } + [Fact] + public async Task FieldAndProperty2() + { + var markup = """ + public static class TestClass + { + private static SimpleRange[] _ranges; + + public static void Method() + { + _ranges = + [ + new() + { + Start = 1, + End = 3, + }, + new() + { + $$ + }, + ]; + } + } + + public struct SimpleRange + { + public int Start; + public int End { get; set; } + }; + """; + + await VerifyItemExistsAsync(markup, "Start"); + await VerifyItemExistsAsync(markup, "End"); + await VerifyExclusiveAsync(markup, true); + } + [Fact] public async Task HidePreviouslyTyped() { From 70b4083f23ee1e74ca43e75a189da56f36702e68 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 12 Mar 2024 15:02:42 -0700 Subject: [PATCH 039/170] Update Language Feature Status for Params Collections (#72516) --- docs/Language Feature Status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 456905c829ce6..b7651f0632ca6 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -11,7 +11,6 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | -| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | [ParamsCollections](https://github.com/dotnet/roslyn/tree/features/ParamsCollections) | [In Progress](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | | [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [Params Span\ + Stackalloc any array type](https://github.com/dotnet/csharplang/issues/1757) | [params-span](https://github.com/dotnet/roslyn/tree/features/params-span) | [In Progress](https://github.com/dotnet/roslyn/issues/57049) | [cston](https://github.com/cston) | TBD | | [jaredpar](https://github.com/jaredpar) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [In Progress](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | | [jcouv](https://github.com/jcouv) | @@ -20,6 +19,7 @@ efforts behind them. | [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | [Merged into 17.9p2](https://github.com/dotnet/roslyn/issues/69432) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | [jcouv](https://github.com/jcouv) | | [`Lock` object](https://github.com/dotnet/csharplang/issues/7104) | [LockObject](https://github.com/dotnet/roslyn/tree/features/LockObject) | [Merged into 17.10p2](https://github.com/dotnet/roslyn/issues/71888) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | | [stephentoub](https://github.com/stephentoub) | | Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | | +| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | main | [Merged to 17.10p3](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | # C# 12.0 From 42a391b18248208a8f222a5fd9ff69224ffa633e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 17:04:46 -0700 Subject: [PATCH 040/170] in proc --- .../Host/RemoteWorkspace_SolutionCaching.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index 55139168584a5..3a6f6ae3d87f7 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -8,6 +8,76 @@ namespace Microsoft.CodeAnalysis.Remote { + /// + /// Not threadsafe. Only use while under a lock. + /// + internal sealed class RemoteSolutionCache + { + private sealed class SolutionCacheNode + { + public readonly Checksum Checksum; + public Solution? Solution; + + public SolutionCacheNode(Checksum checksum) + { + Checksum = checksum; + } + } + + /// + /// The max number of solution instances we'll hold onto at a time. + /// + private const int MaxCapacity = 4; + + /// + /// The total history kept. Used to record telemetry about how useful it would be to increase the max capacity. + /// + private const int TotalHistory = 16; + + private readonly LinkedList _cacheNodes = new(); + + public void Add(Checksum checksum, Solution solution) + { + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + if (current.Value.Checksum == checksum) + { + // Found the item. Take it, move it to the front, and ensure it's pointing at this solution. + _cacheNodes.Remove(current); + _cacheNodes.AddFirst(current); + + // Keep track if we would have found this if the cache was larger + var cacheMiss = current.Value.Solution == null; + current.Value.Solution = solution; + + DropExcessItems(); + return; + } + } + + // Didn't find the item at all. Just add to the front. + _cacheNodes.AddFirst(new SolutionCacheNode(checksum) { Solution = solution }); + DropExcessItems(); + + // Ensure we're not storing too much history. + if (_cacheNodes.Count > TotalHistory) + _cacheNodes.RemoveLast(); + } + + private void DropExcessItems() + { + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + // Don't have to keep going once we stop having solutions. + if (current.Value.Solution is null) + return; + + if (index == MaxCapacity) + } + } + internal sealed partial class RemoteWorkspace { /// From 4f55d5400114c6254468e5659c2b7a6412debab0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 17:30:17 -0700 Subject: [PATCH 041/170] in proc --- .../Host/RemoteWorkspace_SolutionCaching.cs | 100 +++++++++++++----- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index 3a6f6ae3d87f7..d74c3271067f8 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Microsoft.CodeAnalysis.Internal.Log; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote @@ -13,6 +14,40 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class RemoteSolutionCache { + /// + /// The max number of solution instances we'll hold onto at a time. + /// + private const int MaxCapacity = 4; + + /// + /// The total history kept. Used to record telemetry about how useful it would be to increase the max capacity. + /// + private const int TotalHistory = 16; + + /// + /// Keep track of when we find a checksum in the history, but we've dropped the solution for it. This will help + /// us determine what benefit we would get from expanding this cache. + /// + private readonly HistogramLogAggregator.HistogramCounter _cacheMissAggregator = + new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: TotalHistory + 1); + + /// + /// The number of times we successfully found a solution. + /// + private int _cacheHits; + + /// + /// The number of times we failed to find a solution, but could have found it if we cached more items (up to + /// TotalHistory). When this happens, we also store in which bucket it was + /// found in to help us decide what a good cache value is. + /// + private int _cacheMissesInHistory; + + /// + /// The number of times we failed to find a solution, and would not have found it even if we didn't cache more items. + /// + private int _cacheMissesNotInHistory; + private sealed class SolutionCacheNode { public readonly Checksum Checksum; @@ -24,19 +59,9 @@ public SolutionCacheNode(Checksum checksum) } } - /// - /// The max number of solution instances we'll hold onto at a time. - /// - private const int MaxCapacity = 4; - - /// - /// The total history kept. Used to record telemetry about how useful it would be to increase the max capacity. - /// - private const int TotalHistory = 16; - private readonly LinkedList _cacheNodes = new(); - public void Add(Checksum checksum, Solution solution) + private void FindAndMoveNodeToFront(Checksum checksum) { var index = 0; for (var current = _cacheNodes.First; current != null; current = current.Next, index++) @@ -48,35 +73,56 @@ public void Add(Checksum checksum, Solution solution) _cacheNodes.AddFirst(current); // Keep track if we would have found this if the cache was larger - var cacheMiss = current.Value.Solution == null; - current.Value.Solution = solution; - - DropExcessItems(); + if (current.Value.Solution == null) + { + _cacheHits++; + } + else + { + _cacheMissesInHistory++; + _cacheMissAggregator.IncreaseCount(index); + } return; } } // Didn't find the item at all. Just add to the front. - _cacheNodes.AddFirst(new SolutionCacheNode(checksum) { Solution = solution }); - DropExcessItems(); - - // Ensure we're not storing too much history. - if (_cacheNodes.Count > TotalHistory) - _cacheNodes.RemoveLast(); + // + // Note: we don't record + _cacheNodes.AddFirst(new SolutionCacheNode(checksum)); + _cacheMissesNotInHistory++; + return; } - private void DropExcessItems() + public void Add(Checksum checksum, Solution solution) { + Contract.ThrowIfTrue(_cacheNodes.Count > TotalHistory); + + FindAndMoveNodeToFront(checksum); + + Contract.ThrowIfTrue(_cacheNodes.Count > TotalHistory + 1); + Contract.ThrowIfNull(_cacheNodes.First); + Contract.ThrowIfTrue(_cacheNodes.First.Value.Checksum != checksum); + + // Ensure we're holding onto the solution. + _cacheNodes.First.Value.Solution = solution; + + // Now, if our history is too long, remove the last item. + if (_cacheNodes.Count == TotalHistory + 1) + _cacheNodes.RemoveLast(); + + // Finally, ensure that only the first `MaxCapacity` are pointing at solutions and the rest are not. var index = 0; for (var current = _cacheNodes.First; current != null; current = current.Next, index++) { - // Don't have to keep going once we stop having solutions. - if (current.Value.Solution is null) - return; - - if (index == MaxCapacity) + if (index > MaxCapacity) + { + current.Value.Solution = null; + break; + } } } + } internal sealed partial class RemoteWorkspace { From 3538ab62a98523e5f0ca3d2cb901897bb6a677f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 17:32:40 -0700 Subject: [PATCH 042/170] in proc --- src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs | 2 +- .../ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index b68de4597e65d..89ba049c3e282 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -164,7 +164,7 @@ await RunWithSolutionAsync( if (updatePrimaryBranch) _lastRequestedPrimaryBranchSolution = (solutionChecksum, solution); else - _lastRequestedAnyBranchSolution = (solutionChecksum, solution); + _lastRequestedAnyBranchSolutions.Add(solutionChecksum, solution); } // Now, pass it to the callback to do the work. Any other callers into us will be able to benefit from diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index d74c3271067f8..da3693456df6f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -137,7 +137,7 @@ internal sealed partial class RemoteWorkspace /// The last solution requested by a service. Cached as it's very common to have a flurry of requests for the /// same checksum that don't run concurrently. Only read/write while holding . /// - private (Checksum checksum, Solution solution) _lastRequestedAnyBranchSolution; + private readonly RemoteSolutionCache _lastRequestedAnyBranchSolutions = new(); /// /// Mapping from solution-checksum to the solution computed for it. This is used so that we can hold a solution @@ -196,9 +196,9 @@ InFlightSolution GetOrCreateSolutionAndAddInFlightCount_NoLock() // See if we're being asked for a checksum we already have cached a solution for. Safe to read directly // as we're holding _gate. - var cachedSolution = - _lastRequestedPrimaryBranchSolution.checksum == solutionChecksum ? _lastRequestedPrimaryBranchSolution.solution : - _lastRequestedAnyBranchSolution.checksum == solutionChecksum ? _lastRequestedAnyBranchSolution.solution : null; + var cachedSolution = _lastRequestedPrimaryBranchSolution.checksum == solutionChecksum + ? _lastRequestedPrimaryBranchSolution.solution + : _lastRequestedAnyBranchSolutions.Find(solutionChecksum); // We're the first call that is asking about this checksum. Kick off async computation to compute it // (or use an existing cached value we already have). Start with an in-flight-count of 1 to represent From afd29eb622cd7373d2dbd3875c57f98efcd60027 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 17:47:19 -0700 Subject: [PATCH 043/170] in proc --- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 2 +- .../Host/RemoteWorkspace_SolutionCaching.cs | 46 ++++++++++++------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 89ba049c3e282..0471dfcde019e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -48,7 +48,7 @@ public AssetProvider CreateAssetProvider(Checksum solutionChecksum, SolutionAsse /// /// Syncs over the solution corresponding to and sets it as the current /// solution for workspace. This will also end up updating and , allowing + /// cref="_lastRequestedAnyBranchSolutions"/> and , allowing /// them to be pre-populated for feature requests that come in soon after this call completes. /// public async Task UpdatePrimaryBranchSolutionAsync( diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index da3693456df6f..51c840984e8db 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -28,7 +28,7 @@ internal sealed class RemoteSolutionCache /// Keep track of when we find a checksum in the history, but we've dropped the solution for it. This will help /// us determine what benefit we would get from expanding this cache. /// - private readonly HistogramLogAggregator.HistogramCounter _cacheMissAggregator = + private readonly HistogramLogAggregator.HistogramCounter _cacheMissCounter = new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: TotalHistory + 1); /// @@ -71,27 +71,12 @@ private void FindAndMoveNodeToFront(Checksum checksum) // Found the item. Take it, move it to the front, and ensure it's pointing at this solution. _cacheNodes.Remove(current); _cacheNodes.AddFirst(current); - - // Keep track if we would have found this if the cache was larger - if (current.Value.Solution == null) - { - _cacheHits++; - } - else - { - _cacheMissesInHistory++; - _cacheMissAggregator.IncreaseCount(index); - } return; } } // Didn't find the item at all. Just add to the front. - // - // Note: we don't record _cacheNodes.AddFirst(new SolutionCacheNode(checksum)); - _cacheMissesNotInHistory++; - return; } public void Add(Checksum checksum, Solution solution) @@ -122,6 +107,35 @@ public void Add(Checksum checksum, Solution solution) } } } + + public Solution? Find(Checksum checksum) + { + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + if (current.Value.Checksum == checksum) + { + // Found it! + if (current.Value.Solution is null) + { + // Track that we would have been able to return this if our history was longer. + _cacheMissesInHistory++; + _cacheMissCounter.IncreaseCount(index); + } + else + { + // Success! + _cacheHits++; + } + + return current.Value.Solution; + } + } + + // Couldn't find it at all, even in the history. + _cacheMissesNotInHistory++; + return null; + } } internal sealed partial class RemoteWorkspace From a7399930c8443625d40163d2e1766935eb461937 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:02:05 -0700 Subject: [PATCH 044/170] in progress --- .../Host/RemoteWorkspace_SolutionCaching.cs | 17 ++++++++++++++--- .../Compiler/Core/Log/FunctionId.cs | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index 51c840984e8db..fa8fc7bd2fadf 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -38,7 +38,7 @@ internal sealed class RemoteSolutionCache /// /// The number of times we failed to find a solution, but could have found it if we cached more items (up to - /// TotalHistory). When this happens, we also store in which bucket it was + /// TotalHistory). When this happens, we also store in which bucket it was /// found in to help us decide what a good cache value is. /// private int _cacheMissesInHistory; @@ -136,6 +136,17 @@ public void Add(Checksum checksum, Solution solution) _cacheMissesNotInHistory++; return null; } + + public void ReportTelemetry() + { + Logger.Log(FunctionId.RemoteWorkspace_SolutionCachingStatistics, KeyValueLogMessage.Create(m => + { + m.Add(nameof(_cacheHits), _cacheHits); + m.Add(nameof(_cacheMissesInHistory), _cacheMissesInHistory); + m.Add(nameof(_cacheMissesNotInHistory), _cacheMissesNotInHistory); + _cacheMissCounter.WriteTelemetryPropertiesTo(m, prefix: nameof(_cacheMissCounter)); + })); + } } internal sealed partial class RemoteWorkspace @@ -148,8 +159,8 @@ internal sealed partial class RemoteWorkspace private (Checksum checksum, Solution solution) _lastRequestedPrimaryBranchSolution; /// - /// The last solution requested by a service. Cached as it's very common to have a flurry of requests for the - /// same checksum that don't run concurrently. Only read/write while holding . + /// Cache of last N solutions requested by a service. Cached as it's very common to have a flurry of requests + /// for the same few checksum that don't run concurrently. Only read/write while holding . /// private readonly RemoteSolutionCache _lastRequestedAnyBranchSolutions = new(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 856fee04a36e8..10f517d82a91f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -619,4 +619,6 @@ internal enum FunctionId SuggestedAction_Preview_Summary = 745, LSP_DocumentIdCacheMiss = 746, + + RemoteWorkspace_SolutionCachingStatistics = 750, } From 762c01a77553e834136873bf9080d641444e816f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:09:14 -0700 Subject: [PATCH 045/170] Make generic --- .../ServiceHub/Host/RemoteSolutionCache.cs | 152 ++++++++++++++++++ .../Host/RemoteWorkspace_SolutionCaching.cs | 141 +--------------- 2 files changed, 153 insertions(+), 140 deletions(-) create mode 100644 src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs new file mode 100644 index 0000000000000..b6031f8617938 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -0,0 +1,152 @@ +// 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.Generic; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// Not threadsafe. Only use while under a lock. +/// +internal sealed class RemoteSolutionCache + where TChecksum : struct + where TSolution : class +{ + /// + /// The max number of solution instances we'll hold onto at a time. + /// + private readonly int _maxCapacity; + + /// + /// The total history kept. Used to record telemetry about how useful it would be to increase the max capacity. + /// + private readonly int _totalHistory; + + /// + /// Keep track of when we find a checksum in the history, but we've dropped the solution for it. This will help + /// us determine what benefit we would get from expanding this cache. + /// + private readonly HistogramLogAggregator.HistogramCounter _cacheMissCounter; + + /// + /// The number of times we successfully found a solution. + /// + private int _cacheHits; + + /// + /// The number of times we failed to find a solution, but could have found it if we cached more items (up to + /// TotalHistory). When this happens, we also store in which bucket it was + /// found in to help us decide what a good cache value is. + /// + private int _cacheMissesInHistory; + + /// + /// The number of times we failed to find a solution, and would not have found it even if we didn't cache more items. + /// + private int _cacheMissesNotInHistory; + + private readonly LinkedList _cacheNodes = new(); + + public RemoteSolutionCache(int maxCapacity = 4, int totalHistory = 16) + { + _maxCapacity = maxCapacity; + _totalHistory = totalHistory; + _cacheMissCounter = new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: _totalHistory + 1); + } + + private void FindAndMoveNodeToFront(TChecksum checksum) + { + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + if (current.Value.Checksum.Equals(checksum)) + { + // Found the item. Take it, move it to the front, and ensure it's pointing at this solution. + _cacheNodes.Remove(current); + _cacheNodes.AddFirst(current); + return; + } + } + + // Didn't find the item at all. Just add to the front. + _cacheNodes.AddFirst(new CacheNode(checksum)); + } + + public void Add(TChecksum checksum, TSolution solution) + { + Contract.ThrowIfTrue(_cacheNodes.Count > _totalHistory); + + FindAndMoveNodeToFront(checksum); + + Contract.ThrowIfTrue(_cacheNodes.Count > _totalHistory + 1); + Contract.ThrowIfNull(_cacheNodes.First); + Contract.ThrowIfFalse(_cacheNodes.First.Value.Checksum.Equals(checksum)); + + // Ensure we're holding onto the solution. + _cacheNodes.First.Value.Solution = solution; + + // Now, if our history is too long, remove the last item. + if (_cacheNodes.Count == _totalHistory + 1) + _cacheNodes.RemoveLast(); + + // Finally, ensure that only the first `MaxCapacity` are pointing at solutions and the rest are not. + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + if (index > _maxCapacity) + { + current.Value.Solution = null; + break; + } + } + } + + public TSolution? Find(Checksum checksum) + { + var index = 0; + for (var current = _cacheNodes.First; current != null; current = current.Next, index++) + { + if (current.Value.Checksum.Equals(checksum)) + { + // Found it! + if (current.Value.Solution is null) + { + // Track that we would have been able to return this if our history was longer. + _cacheMissesInHistory++; + _cacheMissCounter.IncreaseCount(index); + } + else + { + // Success! + _cacheHits++; + } + + return current.Value.Solution; + } + } + + // Couldn't find it at all, even in the history. + _cacheMissesNotInHistory++; + return null; + } + + public void ReportTelemetry() + { + Logger.Log(FunctionId.RemoteWorkspace_SolutionCachingStatistics, KeyValueLogMessage.Create(m => + { + m.Add(nameof(_cacheHits), _cacheHits); + m.Add(nameof(_cacheMissesInHistory), _cacheMissesInHistory); + m.Add(nameof(_cacheMissesNotInHistory), _cacheMissesNotInHistory); + _cacheMissCounter.WriteTelemetryPropertiesTo(m, prefix: nameof(_cacheMissCounter)); + })); + } + + private sealed class CacheNode(TChecksum checksum) + { + public readonly TChecksum Checksum = checksum; + public TSolution? Solution; + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs index fa8fc7bd2fadf..d6c82aee29f98 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace_SolutionCaching.cs @@ -9,145 +9,6 @@ namespace Microsoft.CodeAnalysis.Remote { - /// - /// Not threadsafe. Only use while under a lock. - /// - internal sealed class RemoteSolutionCache - { - /// - /// The max number of solution instances we'll hold onto at a time. - /// - private const int MaxCapacity = 4; - - /// - /// The total history kept. Used to record telemetry about how useful it would be to increase the max capacity. - /// - private const int TotalHistory = 16; - - /// - /// Keep track of when we find a checksum in the history, but we've dropped the solution for it. This will help - /// us determine what benefit we would get from expanding this cache. - /// - private readonly HistogramLogAggregator.HistogramCounter _cacheMissCounter = - new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: TotalHistory + 1); - - /// - /// The number of times we successfully found a solution. - /// - private int _cacheHits; - - /// - /// The number of times we failed to find a solution, but could have found it if we cached more items (up to - /// TotalHistory). When this happens, we also store in which bucket it was - /// found in to help us decide what a good cache value is. - /// - private int _cacheMissesInHistory; - - /// - /// The number of times we failed to find a solution, and would not have found it even if we didn't cache more items. - /// - private int _cacheMissesNotInHistory; - - private sealed class SolutionCacheNode - { - public readonly Checksum Checksum; - public Solution? Solution; - - public SolutionCacheNode(Checksum checksum) - { - Checksum = checksum; - } - } - - private readonly LinkedList _cacheNodes = new(); - - private void FindAndMoveNodeToFront(Checksum checksum) - { - var index = 0; - for (var current = _cacheNodes.First; current != null; current = current.Next, index++) - { - if (current.Value.Checksum == checksum) - { - // Found the item. Take it, move it to the front, and ensure it's pointing at this solution. - _cacheNodes.Remove(current); - _cacheNodes.AddFirst(current); - return; - } - } - - // Didn't find the item at all. Just add to the front. - _cacheNodes.AddFirst(new SolutionCacheNode(checksum)); - } - - public void Add(Checksum checksum, Solution solution) - { - Contract.ThrowIfTrue(_cacheNodes.Count > TotalHistory); - - FindAndMoveNodeToFront(checksum); - - Contract.ThrowIfTrue(_cacheNodes.Count > TotalHistory + 1); - Contract.ThrowIfNull(_cacheNodes.First); - Contract.ThrowIfTrue(_cacheNodes.First.Value.Checksum != checksum); - - // Ensure we're holding onto the solution. - _cacheNodes.First.Value.Solution = solution; - - // Now, if our history is too long, remove the last item. - if (_cacheNodes.Count == TotalHistory + 1) - _cacheNodes.RemoveLast(); - - // Finally, ensure that only the first `MaxCapacity` are pointing at solutions and the rest are not. - var index = 0; - for (var current = _cacheNodes.First; current != null; current = current.Next, index++) - { - if (index > MaxCapacity) - { - current.Value.Solution = null; - break; - } - } - } - - public Solution? Find(Checksum checksum) - { - var index = 0; - for (var current = _cacheNodes.First; current != null; current = current.Next, index++) - { - if (current.Value.Checksum == checksum) - { - // Found it! - if (current.Value.Solution is null) - { - // Track that we would have been able to return this if our history was longer. - _cacheMissesInHistory++; - _cacheMissCounter.IncreaseCount(index); - } - else - { - // Success! - _cacheHits++; - } - - return current.Value.Solution; - } - } - - // Couldn't find it at all, even in the history. - _cacheMissesNotInHistory++; - return null; - } - - public void ReportTelemetry() - { - Logger.Log(FunctionId.RemoteWorkspace_SolutionCachingStatistics, KeyValueLogMessage.Create(m => - { - m.Add(nameof(_cacheHits), _cacheHits); - m.Add(nameof(_cacheMissesInHistory), _cacheMissesInHistory); - m.Add(nameof(_cacheMissesNotInHistory), _cacheMissesNotInHistory); - _cacheMissCounter.WriteTelemetryPropertiesTo(m, prefix: nameof(_cacheMissCounter)); - })); - } - } internal sealed partial class RemoteWorkspace { @@ -162,7 +23,7 @@ internal sealed partial class RemoteWorkspace /// Cache of last N solutions requested by a service. Cached as it's very common to have a flurry of requests /// for the same few checksum that don't run concurrently. Only read/write while holding . /// - private readonly RemoteSolutionCache _lastRequestedAnyBranchSolutions = new(); + private readonly RemoteSolutionCache _lastRequestedAnyBranchSolutions = new(); /// /// Mapping from solution-checksum to the solution computed for it. This is used so that we can hold a solution From d45336a1538b773a287b869f4a1026c8f1e8b115 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:23:16 -0700 Subject: [PATCH 046/170] Add tests --- .../Remote/RemoteSolutionCacheTests.cs | 137 ++++++++++++++++++ .../ServiceHub/Host/RemoteSolutionCache.cs | 5 +- 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/VisualStudio/Core/Test.Next/Remote/RemoteSolutionCacheTests.cs diff --git a/src/VisualStudio/Core/Test.Next/Remote/RemoteSolutionCacheTests.cs b/src/VisualStudio/Core/Test.Next/Remote/RemoteSolutionCacheTests.cs new file mode 100644 index 0000000000000..a4ea86a254f6f --- /dev/null +++ b/src/VisualStudio/Core/Test.Next/Remote/RemoteSolutionCacheTests.cs @@ -0,0 +1,137 @@ +// 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 Microsoft.CodeAnalysis.Remote; +using Xunit; + +namespace Roslyn.VisualStudio.Next.UnitTests.Remote; + +public class RemoteSolutionCacheTests +{ + [Fact] + public void TestAddAndFind1() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + Assert.Equal("1", cache.Find(1)); + Assert.Null(cache.Find(2)); + } + + [Fact] + public void TestAddAndFindOverwrite1() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + cache.Add(1, "2"); + Assert.Equal("2", cache.Find(1)); + Assert.Null(cache.Find(2)); + } + + [Fact] + public void TestAddAndFindOverwrite2() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + cache.Add(2, "2"); + cache.Add(1, "3"); + Assert.Equal("3", cache.Find(1)); + Assert.Equal("2", cache.Find(2)); + Assert.Null(cache.Find(3)); + } + + [Fact] + public void TestAddAndFindFour() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + cache.Add(2, "2"); + cache.Add(3, "3"); + cache.Add(4, "4"); + Assert.Equal("1", cache.Find(1)); + Assert.Equal("2", cache.Find(2)); + Assert.Equal("3", cache.Find(3)); + Assert.Equal("4", cache.Find(4)); + Assert.Null(cache.Find(5)); + } + + [Fact] + public void TestAddAndFindFive_A() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + cache.Add(2, "2"); + cache.Add(3, "3"); + cache.Add(4, "4"); + cache.Add(5, "5"); + Assert.Null(cache.Find(1)); + Assert.Equal("2", cache.Find(2)); + Assert.Equal("3", cache.Find(3)); + Assert.Equal("4", cache.Find(4)); + Assert.Equal("5", cache.Find(5)); + Assert.Null(cache.Find(6)); + } + + [Fact] + public void TestAddAndFindFive_B() + { + var cache = new RemoteSolutionCache(maxCapacity: 4); + cache.Add(1, "1"); + cache.Add(2, "2"); + cache.Add(3, "3"); + cache.Add(4, "4"); + cache.Add(1, "1"); // re-add. should ensure that this doesn't fall out of the cache. + cache.Add(5, "5"); + Assert.Equal("1", cache.Find(1)); + Assert.Null(cache.Find(2)); + Assert.Equal("3", cache.Find(3)); + Assert.Equal("4", cache.Find(4)); + Assert.Equal("5", cache.Find(5)); + Assert.Null(cache.Find(6)); + } + + [Fact] + public void TestLargeHistory_A() + { + var cache = new RemoteSolutionCache(maxCapacity: 4, totalHistory: 16); + + for (var i = 0; i < 20; i++) + cache.Add(i, $"{i}"); + + for (var i = 0; i < 20; i++) + { + if (i < 16) + { + Assert.Null(cache.Find(i)); + } + else + { + Assert.Equal($"{i}", cache.Find(i)); + } + } + } + + [Fact] + public void TestLargeHistory_B() + { + var cache = new RemoteSolutionCache(maxCapacity: 4, totalHistory: 16); + + for (var i = 0; i < 20; i++) + cache.Add(i, $"{i}"); + + for (var i = 20 - 1; i >= 0; i--) + cache.Add(i, $"{i}"); + + for (var i = 0; i < 20; i++) + { + if (i >= 4) + { + Assert.Null(cache.Find(i)); + } + else + { + Assert.Equal($"{i}", cache.Find(i)); + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index b6031f8617938..cf76645d9d5cd 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -96,7 +96,7 @@ public void Add(TChecksum checksum, TSolution solution) var index = 0; for (var current = _cacheNodes.First; current != null; current = current.Next, index++) { - if (index > _maxCapacity) + if (index >= _maxCapacity) { current.Value.Solution = null; break; @@ -104,8 +104,9 @@ public void Add(TChecksum checksum, TSolution solution) } } - public TSolution? Find(Checksum checksum) + public TSolution? Find(TChecksum checksum) { + // Note: we intentionally do not move an item when we find it. That's because var index = 0; for (var current = _cacheNodes.First; current != null; current = current.Next, index++) { From b8bd8786bee6f9bc865f196210a12b4fe3645004 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:24:03 -0700 Subject: [PATCH 047/170] Comment --- src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index cf76645d9d5cd..c8817b4c14d9b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -106,7 +106,8 @@ public void Add(TChecksum checksum, TSolution solution) public TSolution? Find(TChecksum checksum) { - // Note: we intentionally do not move an item when we find it. That's because + // Note: we intentionally do not move an item when we find it. That's because our caller will always call 'Add' + // with the found solution afterwards. This will ensure that that solution makes it to the front of the line. var index = 0; for (var current = _cacheNodes.First; current != null; current = current.Next, index++) { From 405abde013064ac40815d721dc48e35ecb1450f0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:27:23 -0700 Subject: [PATCH 048/170] Docs --- .../Remote/ServiceHub/Host/RemoteSolutionCache.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index c8817b4c14d9b..d997c6090bcbd 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -9,6 +9,8 @@ namespace Microsoft.CodeAnalysis.Remote; /// +/// LRU cache of checksum+solution pairs. Used to keep track of the last few solutions the remote server knows about, +/// helping to avoid unnecessary syncs/recreations of those solutions while many requests are coming into the server. /// Not threadsafe. Only use while under a lock. /// internal sealed class RemoteSolutionCache @@ -48,6 +50,11 @@ internal sealed class RemoteSolutionCache /// private int _cacheMissesNotInHistory; + /// + /// The list of checksum+solution pairs. Note: only the first items will actually point + /// at a non-null solution. The ones after that will point at . We store both so that we can + /// collect useful telemetry on how much benefit we would get by having a larger history. + /// private readonly LinkedList _cacheNodes = new(); public RemoteSolutionCache(int maxCapacity = 4, int totalHistory = 16) From f5038ee081aa6861f61c839880041e9c845441e3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:29:09 -0700 Subject: [PATCH 049/170] Update src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs --- src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index d997c6090bcbd..f52abbcab22f4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -71,7 +71,7 @@ private void FindAndMoveNodeToFront(TChecksum checksum) { if (current.Value.Checksum.Equals(checksum)) { - // Found the item. Take it, move it to the front, and ensure it's pointing at this solution. + // Found the item. Take it, move it to the front. _cacheNodes.Remove(current); _cacheNodes.AddFirst(current); return; From 77871ac34a567bc1e6d5e9a4a3c6d2abb601dd23 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 18:35:57 -0700 Subject: [PATCH 050/170] Store all indices --- .../ServiceHub/Host/RemoteSolutionCache.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index d997c6090bcbd..f22aa81b4da73 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -28,20 +28,20 @@ internal sealed class RemoteSolutionCache private readonly int _totalHistory; /// - /// Keep track of when we find a checksum in the history, but we've dropped the solution for it. This will help - /// us determine what benefit we would get from expanding this cache. + /// Keep track of what index we found find a checksum at in the history. This will help us tell both if the cache + /// is too large, or if it's too small. /// - private readonly HistogramLogAggregator.HistogramCounter _cacheMissCounter; + private readonly HistogramLogAggregator.HistogramCounter _cacheHitIndexHistogram; /// - /// The number of times we successfully found a solution. + /// The number of times we successfully found a solution. When this happens we'll increment . /// private int _cacheHits; /// /// The number of times we failed to find a solution, but could have found it if we cached more items (up to - /// TotalHistory). When this happens, we also store in which bucket it was - /// found in to help us decide what a good cache value is. + /// TotalHistory). When this happens we'll increment . /// private int _cacheMissesInHistory; @@ -61,7 +61,7 @@ public RemoteSolutionCache(int maxCapacity = 4, int totalHistory = 16) { _maxCapacity = maxCapacity; _totalHistory = totalHistory; - _cacheMissCounter = new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: _totalHistory + 1); + _cacheHitIndexHistogram = new(bucketSize: 1, maxBucketValue: int.MaxValue, bucketCount: _totalHistory + 1); } private void FindAndMoveNodeToFront(TChecksum checksum) @@ -125,7 +125,6 @@ public void Add(TChecksum checksum, TSolution solution) { // Track that we would have been able to return this if our history was longer. _cacheMissesInHistory++; - _cacheMissCounter.IncreaseCount(index); } else { @@ -133,6 +132,7 @@ public void Add(TChecksum checksum, TSolution solution) _cacheHits++; } + _cacheHitIndexHistogram.IncreaseCount(index); return current.Value.Solution; } } @@ -149,7 +149,7 @@ public void ReportTelemetry() m.Add(nameof(_cacheHits), _cacheHits); m.Add(nameof(_cacheMissesInHistory), _cacheMissesInHistory); m.Add(nameof(_cacheMissesNotInHistory), _cacheMissesNotInHistory); - _cacheMissCounter.WriteTelemetryPropertiesTo(m, prefix: nameof(_cacheMissCounter)); + _cacheHitIndexHistogram.WriteTelemetryPropertiesTo(m, prefix: nameof(_cacheHitIndexHistogram)); })); } From 2c3db16df15938842bd498bf0948b4c0e97787ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 19:00:52 -0700 Subject: [PATCH 051/170] Simplify base case --- .../Remote/ServiceHub/Host/RemoteSolutionCache.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs index accc70bf5b6ca..75660559eabdc 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteSolutionCache.cs @@ -71,9 +71,14 @@ private void FindAndMoveNodeToFront(TChecksum checksum) { if (current.Value.Checksum.Equals(checksum)) { - // Found the item. Take it, move it to the front. - _cacheNodes.Remove(current); - _cacheNodes.AddFirst(current); + // Found the item. + if (index > 0) + { + // If it's not already at the front, move it there. + _cacheNodes.Remove(current); + _cacheNodes.AddFirst(current); + } + return; } } From 98cd7d72ae535ac6ffc00d1ad88fa723bc28d492 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 21:59:13 -0700 Subject: [PATCH 052/170] Enhance supported cases for 'remove redundant operators' --- .../RemoveRedundantEqualityTests.cs | 30 ++++++--- ...moveRedundantEqualityDiagnosticAnalyzer.cs | 60 +++++++++--------- .../RedundantEqualityConstants.cs | 1 + .../RemoveRedundantEqualityCodeFixProvider.cs | 61 +++++++++---------- .../RemoveRedundantEqualityTests.vb | 26 +++++--- 5 files changed, 97 insertions(+), 81 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.cs b/src/Analyzers/CSharp/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.cs index a3f9d594dbf8c..5f432749c27ee 100644 --- a/src/Analyzers/CSharp/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.cs @@ -44,16 +44,23 @@ public bool M1(bool x) [Fact] public async Task TestSimpleCaseForEqualsFalse_NoDiagnostics() { - var code = """ + await VerifyCS.VerifyCodeFixAsync(""" public class C { public bool M1(bool x) { - return x == false; + return x [|==|] false; } } - """; - await VerifyCS.VerifyAnalyzerAsync(code); + """, """ + public class C + { + public bool M1(bool x) + { + return !x; + } + } + """); } [Fact] @@ -83,16 +90,23 @@ public bool M1(bool x) [Fact] public async Task TestSimpleCaseForNotEqualsTrue_NoDiagnostics() { - var code = """ + await VerifyCS.VerifyCodeFixAsync(""" public class C { public bool M1(bool x) { - return x != true; + return x [|!=|] true; } } - """; - await VerifyCS.VerifyAnalyzerAsync(code); + """, """ + public class C + { + public bool M1(bool x) + { + return !x; + } + } + """); } [Fact] diff --git a/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs index de3de1f9883b1..8be778a4b7e10 100644 --- a/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs @@ -10,20 +10,13 @@ namespace Microsoft.CodeAnalysis.RemoveRedundantEquality; -internal abstract class AbstractRemoveRedundantEqualityDiagnosticAnalyzer - : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal abstract class AbstractRemoveRedundantEqualityDiagnosticAnalyzer(ISyntaxFacts syntaxFacts) + : AbstractBuiltInCodeStyleDiagnosticAnalyzer( + IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId, + EnforceOnBuildValues.RemoveRedundantEquality, + option: null, + new LocalizableResourceString(nameof(AnalyzersResources.Remove_redundant_equality), AnalyzersResources.ResourceManager, typeof(AnalyzersResources))) { - private readonly ISyntaxFacts _syntaxFacts; - - protected AbstractRemoveRedundantEqualityDiagnosticAnalyzer(ISyntaxFacts syntaxFacts) - : base(IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId, - EnforceOnBuildValues.RemoveRedundantEquality, - option: null, - new LocalizableResourceString(nameof(AnalyzersResources.Remove_redundant_equality), AnalyzersResources.ResourceManager, typeof(AnalyzersResources))) - { - _syntaxFacts = syntaxFacts; - } - public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; @@ -35,19 +28,15 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context) if (ShouldSkipAnalysis(context, notification: null)) return; + // We shouldn't report diagnostic on overloaded operator as the behavior can change. var operation = (IBinaryOperation)context.Operation; if (operation.OperatorMethod is not null) - { - // We shouldn't report diagnostic on overloaded operator as the behavior can change. return; - } if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals)) - { return; - } - if (!_syntaxFacts.IsBinaryExpression(operation.Syntax)) + if (!syntaxFacts.IsBinaryExpression(operation.Syntax)) { return; } @@ -56,9 +45,7 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context) var leftOperand = operation.LeftOperand; if (rightOperand.Type is null || leftOperand.Type is null) - { return; - } if (rightOperand.Type.SpecialType != SpecialType.System_Boolean || leftOperand.Type.SpecialType != SpecialType.System_Boolean) @@ -67,25 +54,31 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context) } var isOperatorEquals = operation.OperatorKind == BinaryOperatorKind.Equals; - _syntaxFacts.GetPartsOfBinaryExpression(operation.Syntax, out _, out var operatorToken, out _); + syntaxFacts.GetPartsOfBinaryExpression(operation.Syntax, out _, out var operatorToken, out _); + var properties = ImmutableDictionary.CreateBuilder(); - if (TryGetLiteralValue(rightOperand) == isOperatorEquals) + if (TryGetLiteralValue(rightOperand) is bool rightBool) { properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Right); + if (rightBool != isOperatorEquals) + properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate); } - else if (TryGetLiteralValue(leftOperand) == isOperatorEquals) + else if (TryGetLiteralValue(leftOperand) is bool leftBool) { properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Left); + if (leftBool != isOperatorEquals) + properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate); } - - if (properties.Count == 1) + else { - context.ReportDiagnostic(Diagnostic.Create(Descriptor, - operatorToken.GetLocation(), - additionalLocations: [operation.Syntax.GetLocation()], - properties: properties.ToImmutable())); + return; } + context.ReportDiagnostic(Diagnostic.Create(Descriptor, + operatorToken.GetLocation(), + additionalLocations: [operation.Syntax.GetLocation()], + properties: properties.ToImmutable())); + return; static bool? TryGetLiteralValue(IOperation operand) @@ -93,8 +86,11 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context) // Make sure we only simplify literals to avoid changing // something like the following example: // const bool Activated = true; ... if (state == Activated) - if (operand.ConstantValue.HasValue && operand.Kind == OperationKind.Literal && - operand.ConstantValue.Value is bool constValue) + if (operand is + { + Kind: OperationKind.Literal, + ConstantValue: { HasValue: true, Value: bool constValue } + }) { return constValue; } diff --git a/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/RedundantEqualityConstants.cs b/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/RedundantEqualityConstants.cs index 9303d7d6b89fc..50629058eb1f5 100644 --- a/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/RedundantEqualityConstants.cs +++ b/src/Analyzers/Core/Analyzers/RemoveRedundantEquality/RedundantEqualityConstants.cs @@ -9,4 +9,5 @@ internal static class RedundantEqualityConstants public const string RedundantSide = nameof(RedundantSide); public const string Left = nameof(Left); public const string Right = nameof(Right); + public const string Negate = nameof(Negate); } diff --git a/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs index b79642762a6bd..6c5c51a7bba7b 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs @@ -21,54 +21,49 @@ namespace Microsoft.CodeAnalysis.RemoveRedundantEquality; [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.RemoveRedundantEquality), Shared] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] -internal sealed class RemoveRedundantEqualityCodeFixProvider() : SyntaxEditorBasedCodeFixProvider +internal sealed class RemoveRedundantEqualityCodeFixProvider() : ForkingSyntaxEditorBasedCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => [IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId]; - public override Task RegisterCodeFixesAsync(CodeFixContext context) - { - foreach (var diagnostic in context.Diagnostics) - { - RegisterCodeFix(context, AnalyzersResources.Remove_redundant_equality, nameof(AnalyzersResources.Remove_redundant_equality), diagnostic); - } + protected override (string title, string equivalenceKey) GetTitleAndEquivalenceKey(CodeFixContext context) + => (AnalyzersResources.Remove_redundant_equality, nameof(AnalyzersResources.Remove_redundant_equality)); - return Task.CompletedTask; - } - - protected override async Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + protected override async Task FixAsync( + Document document, + SyntaxEditor editor, + CodeActionOptionsProvider fallbackOptions, + SyntaxNode node, + ImmutableDictionary properties, + CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + var generatorInternal = document.GetRequiredLanguageService(); + var syntaxFacts = document.GetRequiredLanguageService(); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - foreach (var diagnostic in diagnostics) + editor.ReplaceNode(node, WithElasticTrailingTrivia(RewriteNode())); + + return; + + SyntaxNode RewriteNode() { - var node = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); + // This should happen only in error cases. + if (!syntaxFacts.IsBinaryExpression(node)) + return node; - editor.ReplaceNode(node, (n, _) => - { - if (!syntaxFacts.IsBinaryExpression(n)) - { - // This should happen only in error cases. - return n; - } + syntaxFacts.GetPartsOfBinaryExpression(node, out var left, out var right); + var rewritten = + properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Right ? left : + properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Left ? right : node; - syntaxFacts.GetPartsOfBinaryExpression(n, out var left, out var right); - if (diagnostic.Properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Right) - { - return WithElasticTrailingTrivia(left); - } - else if (diagnostic.Properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Left) - { - return WithElasticTrailingTrivia(right); - } + if (properties.ContainsKey(RedundantEqualityConstants.Negate)) + rewritten = generator.Negate(generatorInternal, rewritten, semanticModel, cancellationToken); - return n; - }); + return rewritten; } - return; - static SyntaxNode WithElasticTrailingTrivia(SyntaxNode node) { return node.WithTrailingTrivia(node.GetTrailingTrivia().Select(SyntaxTriviaExtensions.AsElastic)); diff --git a/src/Analyzers/VisualBasic/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.vb b/src/Analyzers/VisualBasic/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.vb index 9e47f84472cff..994931792e16a 100644 --- a/src/Analyzers/VisualBasic/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.vb +++ b/src/Analyzers/VisualBasic/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.vb @@ -29,14 +29,19 @@ End Module Public Async Function TestSimpleCaseForEqualsFalse_NoDiagnostics() As Task - Dim code = " + Await VerifyVB.VerifyCodeFixAsync(" Public Module Module1 Public Function M1(x As Boolean) As Boolean - Return x = False + Return x [|=|] False End Function End Module -" - Await VerifyVB.VerifyAnalyzerAsync(code) +", " +Public Module Module1 + Public Function M1(x As Boolean) As Boolean + Return Not x + End Function +End Module +") End Function @@ -60,14 +65,19 @@ End Module Public Async Function TestSimpleCaseForNotEqualsTrue_NoDiagnostics() As Task - Dim code = " + Await VerifyVB.VerifyCodeFixAsync(" Public Module Module1 Public Function M1(x As Boolean) As Boolean - Return x <> True + Return x [|<>|] True End Function End Module -" - Await VerifyVB.VerifyAnalyzerAsync(code) +", " +Public Module Module1 + Public Function M1(x As Boolean) As Boolean + Return Not x + End Function +End Module +") End Function From 326db66e66e10f75a4daadd9a1ba73dd6e3b731f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 12 Mar 2024 22:13:31 -0700 Subject: [PATCH 053/170] nrt --- .../RemoveRedundantEqualityCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs index 6c5c51a7bba7b..15e00c76d5987 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs @@ -41,7 +41,7 @@ protected override async Task FixAsync( var generatorInternal = document.GetRequiredLanguageService(); var syntaxFacts = document.GetRequiredLanguageService(); - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); editor.ReplaceNode(node, WithElasticTrailingTrivia(RewriteNode())); From 900a62abb5bc9188423ebeee4ddfc9ce35a1da0c Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Tue, 12 Mar 2024 22:23:13 -0700 Subject: [PATCH 054/170] Use ProvideSettingsManifest to specify Unfied Settings registration file (#72429) * Update versions * Rename registration.json and add ProvideSettingsManifest * Add name * fix test * Fix name * Clean * Add an entry in pkgdef * Add comment * Change csproj * Fix source build * Remove blank line * Add comment * Fix typo --- eng/Directory.Packages.props | 30 +++++++++---------- eng/SourceBuildPrebuiltBaseline.xml | 1 + eng/Version.Details.xml | 4 +++ eng/Versions.props | 1 + .../TestUtilities/StubSettingsManagerHost.cs | 7 ++++- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 1 + ...isualStudio.LanguageServices.CSharp.csproj | 1 + .../CSharp/Impl/PackageRegistration.pkgdef | 7 +++++ .../csharpSettings.registration.json} | 4 +++ .../Setup/Roslyn.VisualStudio.Setup.csproj | 1 - 10 files changed, 40 insertions(+), 17 deletions(-) rename src/VisualStudio/{Setup/UnifiedSettings/csharpIntellisenseSettings.registration.json => CSharp/Impl/UnifiedSettings/csharpSettings.registration.json} (96%) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 9a4458ff7537c..7c204fad33abc 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -43,9 +43,9 @@ - - - + + + @@ -88,32 +88,32 @@ - + - - + + - - - + + + - + - + - + diff --git a/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs b/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs index de4f0a5eb0dcc..d14ce4996f855 100644 --- a/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs +++ b/src/EditorFeatures/TestUtilities/StubSettingsManagerHost.cs @@ -83,7 +83,7 @@ Task ISettingsManagerHost5.GetServiceStreamAsync(string serviceMoniker, throw new NotImplementedException(); } - private sealed class StringStorage : IStringStorage, IAsyncStringStorage + private sealed class StringStorage : IStringStorage2, IAsyncStringStorage { private ImmutableDictionary _values = ImmutableDictionary.Empty; private PropertyChangedEventHandler? _propertyChanged; @@ -179,5 +179,10 @@ Task IAsyncStringStorage.SetAsync(NamedVersionedString valu { throw new NotImplementedException(); } + + void IStringStorage2.ForEachSettingNameStartingWith(string prefix, CharSpanProcessorDelegate processFunc) + { + throw new NotImplementedException(); + } } } diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index 78177323fcaa7..ccae4e77195f6 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,6 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] + [ProvideSettingsManifest(PackageRelativeManifestFile = @"UnifiedSettings\csharpSettings.registration.json")] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { diff --git a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj index c5cfdf4c133f7..6ff52d800af00 100644 --- a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj +++ b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj @@ -18,6 +18,7 @@ + diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index 1a7f62519f085..ee45c4813cee4 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -192,3 +192,10 @@ "Package"="{13C3BBB4-F18F-4111-9F54-A0FB010D9194}" "SortPriority"=dword:00000064 @="Microsoft Visual C#" + +// CacheTag value should be changed when registration file changes +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more infomation +[$RootKey$\SettingsManifests\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] +@="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" +"CacheTag"=qword:08DC1824DFE0117B diff --git a/src/VisualStudio/Setup/UnifiedSettings/csharpIntellisenseSettings.registration.json b/src/VisualStudio/CSharp/Impl/UnifiedSettings/csharpSettings.registration.json similarity index 96% rename from src/VisualStudio/Setup/UnifiedSettings/csharpIntellisenseSettings.registration.json rename to src/VisualStudio/CSharp/Impl/UnifiedSettings/csharpSettings.registration.json index 2253fef5a7a06..a5a62ad827f35 100644 --- a/src/VisualStudio/Setup/UnifiedSettings/csharpIntellisenseSettings.registration.json +++ b/src/VisualStudio/CSharp/Impl/UnifiedSettings/csharpSettings.registration.json @@ -1,3 +1,7 @@ +// NOTE: +// When this file is changed. Please also update the cache tag under settings entry in src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +// Otherwise your change might be ignored. +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more details { "properties": { // CompletionOptionsStorage.TriggerOnTypingLetters diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 9af768dba8071..ee3c8f5f5dc30 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -347,7 +347,6 @@ true false - From 88bd6b2175efb8fb2415f62d30fbbfe5af942ca5 Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Tue, 12 Mar 2024 22:40:08 -0700 Subject: [PATCH 055/170] Create analyzer reference once in ctor --- .../HostDiagnosticAnalyzerProvider.cs | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs index dabf29f8ba48e..cbaa86c9589e5 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs @@ -6,46 +6,31 @@ using System.Reflection; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; internal class HostDiagnosticAnalyzerProvider : IHostDiagnosticAnalyzerProvider { - /// - /// The full path to the Razor source generator - /// - public string? RazorSourceGenerator { get; set; } - private ImmutableArray<(AnalyzerFileReference reference, string extensionId)>? _cachedAnalyzerReferences; + private readonly ImmutableArray<(AnalyzerFileReference reference, string extensionId)> _analyzerReferences; public HostDiagnosticAnalyzerProvider(string? razorSourceGenerator) { - RazorSourceGenerator = razorSourceGenerator; - } - - public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() - { - if (_cachedAnalyzerReferences != null) + if (razorSourceGenerator == null || !File.Exists(razorSourceGenerator)) { - return _cachedAnalyzerReferences.Value; + _analyzerReferences = ImmutableArray<(AnalyzerFileReference reference, string extensionId)>.Empty; } - - if (RazorSourceGenerator == null) + else { - _cachedAnalyzerReferences = ImmutableArray<(AnalyzerFileReference reference, string extensionId)>.Empty; - return _cachedAnalyzerReferences.Value; + _analyzerReferences = ImmutableArray.Create<(AnalyzerFileReference reference, string extensionId)>(( + new AnalyzerFileReference(razorSourceGenerator, new SimpleAnalyzerAssemblyLoader()), + ProjectSystemProject.RazorVsixExtensionId + )); } + } - var analyzerReferences = new List<(AnalyzerFileReference, string)>(); - - // Create an AnalyzerFileReference for each file - var analyzerReference = new AnalyzerFileReference(RazorSourceGenerator, new SimpleAnalyzerAssemblyLoader()); - - // Add the reference to the list, using the file name as the extension ID - analyzerReferences.Add((analyzerReference, ProjectSystemProject.RazorVsixExtensionId)); - - _cachedAnalyzerReferences = analyzerReferences.ToImmutableArray(); - return _cachedAnalyzerReferences.Value; + public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() + { + return _analyzerReferences; } private class SimpleAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader From c40aa6245bc6bdb6ef73ad7858f57f0ecf2c637c Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 13 Mar 2024 10:12:52 +0100 Subject: [PATCH 056/170] Suppress Lock conversion warnings when using object equality operators (#72459) * Suppress Lock conversion warnings when using object equality operators * Use `AccumulatesDiagnostics` helper * Avoid resolving diagnostic code --- .../Portable/Binder/Binder_Operators.cs | 34 +++++- .../CSharp/Test/Emit2/Semantics/LockTests.cs | 100 ++++++++++++++++++ .../Portable/Binding/Binder_Operators.vb | 34 +++++- .../Test/Semantic/Binding/SyncLockTests.vb | 85 +++++++++++++++ 4 files changed, 249 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 4e4347eba66c6..40acd645365d9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -637,8 +637,38 @@ private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, Bi Debug.Assert((object)signature.LeftType != null); Debug.Assert((object)signature.RightType != null); - resultLeft = CreateConversion(left, best.LeftConversion, signature.LeftType, diagnostics); - resultRight = CreateConversion(right, best.RightConversion, signature.RightType, diagnostics); + // If this is an object equality operator, we will suppress Lock conversion warnings. + var needsFilterDiagnostics = + resultOperatorKind is BinaryOperatorKind.ObjectEqual or BinaryOperatorKind.ObjectNotEqual && + diagnostics.AccumulatesDiagnostics; + var conversionDiagnostics = needsFilterDiagnostics ? BindingDiagnosticBag.GetInstance(template: diagnostics) : diagnostics; + + resultLeft = CreateConversion(left, best.LeftConversion, signature.LeftType, conversionDiagnostics); + resultRight = CreateConversion(right, best.RightConversion, signature.RightType, conversionDiagnostics); + + if (needsFilterDiagnostics) + { + Debug.Assert(conversionDiagnostics != diagnostics); + diagnostics.AddDependencies(conversionDiagnostics); + + var sourceBag = conversionDiagnostics.DiagnosticBag; + Debug.Assert(sourceBag is not null); + + if (!sourceBag.IsEmptyWithoutResolution) + { + foreach (var diagnostic in sourceBag.AsEnumerableWithoutResolution()) + { + var code = diagnostic is DiagnosticWithInfo { HasLazyInfo: true, LazyInfo.Code: var lazyCode } ? lazyCode : diagnostic.Code; + if ((ErrorCode)code is not ErrorCode.WRN_ConvertingLock) + { + diagnostics.Add(diagnostic); + } + } + } + + conversionDiagnostics.Free(); + } + resultConstant = FoldBinaryOperator(node, resultOperatorKind, resultLeft, resultRight, resultType, diagnostics); } else diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs index 392bfa04527f5..61b08bdf399e5 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/LockTests.cs @@ -1748,6 +1748,106 @@ .locals init (System.Threading.Lock.Scope V_0) """); } + [Fact] + public void ObjectEquality() + { + var source = """ + #nullable enable + using System; + using System.Threading; + + static class C + { + static void Main() + { + Lock? l = new(); + if (l != null) + { + lock (l) { Console.Write("1"); } + } + if (l == null) + { + throw null!; + } + if (l is { }) + { + lock (l) { Console.Write("2"); } + } + if (l is { } l2) + { + lock (l2) { Console.Write("3"); } + } + if (l is not { }) + { + throw null!; + } + if (l is null) + { + throw null!; + } + if (l is not null) + { + lock (l) { Console.Write("4"); } + } + if (l is not null and var l3) + { + lock (l3) { Console.Write("5"); } + } + if (null != l) + { + lock (l) { Console.Write("6"); } + } + if (null == l) + { + throw null!; + } + if (!(l == null)) + { + lock (l) { Console.Write("7"); } + } + if (!(l != null)) + { + throw null!; + } + + Lock? l4 = new(); + if (l == l4) + { + throw null!; + } + if (l != l4) + { + lock (l4) { Console.Write("8"); } + } + if (ReferenceEquals(l, l4)) + { + throw null!; + } + if (((object)l) == l4) + { + throw null!; + } + if (l == new Lock()) + { + throw null!; + } + } + } + """; + var verifier = CompileAndVerify([source, LockTypeDefinition], verify: Verification.FailsILVerify, + expectedOutput: "E1DE2DE3DE4DE5DE6DE7DE8D"); + verifier.VerifyDiagnostics( + // (68,29): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // if (ReferenceEquals(l, l4)) + Diagnostic(ErrorCode.WRN_ConvertingLock, "l").WithLocation(68, 29), + // (68,32): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // if (ReferenceEquals(l, l4)) + Diagnostic(ErrorCode.WRN_ConvertingLock, "l4").WithLocation(68, 32), + // (72,22): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // if (((object)l) == l4) + Diagnostic(ErrorCode.WRN_ConvertingLock, "l").WithLocation(72, 22)); + } + [Fact] public void Await() { diff --git a/src/Compilers/VisualBasic/Portable/Binding/Binder_Operators.vb b/src/Compilers/VisualBasic/Portable/Binding/Binder_Operators.vb index e9988e68415b8..99cb7ac6d1ad8 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/Binder_Operators.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/Binder_Operators.vb @@ -38,8 +38,38 @@ Namespace Microsoft.CodeAnalysis.VisualBasic left = MakeRValue(left, diagnostics) right = MakeRValue(right, diagnostics) - left = ValidateAndConvertIsExpressionArgument(left, right, [isNot], diagnostics) - right = ValidateAndConvertIsExpressionArgument(right, left, [isNot], diagnostics) + ' Suppress Lock conversion warnings for Is operator. + Dim needsFilterDiagnostics = diagnostics.AccumulatesDiagnostics + Dim conversionDiagnostics = If(needsFilterDiagnostics, BindingDiagnosticBag.GetInstance(diagnostics), diagnostics) + + left = ValidateAndConvertIsExpressionArgument(left, right, [isNot], conversionDiagnostics) + right = ValidateAndConvertIsExpressionArgument(right, left, [isNot], conversionDiagnostics) + + If needsFilterDiagnostics Then + Debug.Assert(conversionDiagnostics IsNot diagnostics) + diagnostics.AddDependencies(conversionDiagnostics) + + Dim sourceBag = conversionDiagnostics.DiagnosticBag + Debug.Assert(sourceBag IsNot Nothing) + + If Not sourceBag.IsEmptyWithoutResolution Then + For Each diagnostic In sourceBag.AsEnumerableWithoutResolution() + Dim code As Integer + Dim diagnosticWithInfo = TryCast(diagnostic, DiagnosticWithInfo) + If diagnosticWithInfo IsNot Nothing AndAlso diagnosticWithInfo.HasLazyInfo Then + code = diagnosticWithInfo.LazyInfo.Code + Else + code = diagnostic.Code + End If + + If code <> ERRID.WRN_ConvertingLock Then + diagnostics.Add(diagnostic) + End If + Next + End If + + conversionDiagnostics.Free() + End If Dim result As BoundExpression Dim booleanType = GetSpecialType(SpecialType.System_Boolean, node, diagnostics) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Binding/SyncLockTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Binding/SyncLockTests.vb index 98f71f7808904..fff1e134bbdcb 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Binding/SyncLockTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Binding/SyncLockTests.vb @@ -984,5 +984,90 @@ BC42508: A value of type 'System.Threading.Lock' converted to a different type w IL_002b: ret }]]>) End Sub + + + Public Sub LockType_ObjectEquality() + Dim source = .Value + Dim comp = CreateCompilation(source, options:=TestOptions.ReleaseExe) + Dim verifier = CompileAndVerify(comp, expectedOutput:="12345") + verifier.Diagnostics.AssertTheseDiagnostics() + End Sub End Class End Namespace From 9be4c180382a6981a2738f2174f79c5cea967634 Mon Sep 17 00:00:00 2001 From: DoctorKrolic <70431552+DoctorKrolic@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:43:16 +0300 Subject: [PATCH 057/170] Fix doc comment (#72514) --- src/Compilers/Core/Portable/Symbols/ISymbol.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/Symbols/ISymbol.cs b/src/Compilers/Core/Portable/Symbols/ISymbol.cs index 285f1ac0efc40..1c461b0825233 100644 --- a/src/Compilers/Core/Portable/Symbols/ISymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/ISymbol.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -191,7 +190,7 @@ public interface ISymbol : IEquatable ImmutableArray DeclaringSyntaxReferences { get; } /// - /// Gets the attributes for the symbol. Returns an empty + /// Gets the attributes for the symbol. Returns an empty /// if there are no attributes. /// ImmutableArray GetAttributes(); From 7537f719ac70eb3a8985d988c25c65b2065e9162 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 13 Mar 2024 07:55:15 -0700 Subject: [PATCH 058/170] Add back interface used by TypeScript --- .../IWorkCoordinatorPriorityService.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/Features/Core/Portable/SolutionCrawler/IWorkCoordinatorPriorityService.cs diff --git a/src/Features/Core/Portable/SolutionCrawler/IWorkCoordinatorPriorityService.cs b/src/Features/Core/Portable/SolutionCrawler/IWorkCoordinatorPriorityService.cs new file mode 100644 index 0000000000000..63ab88a486703 --- /dev/null +++ b/src/Features/Core/Portable/SolutionCrawler/IWorkCoordinatorPriorityService.cs @@ -0,0 +1,21 @@ +// 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; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.SolutionCrawler; + +[Obsolete("Remove your implementation of this interface and let Roslyn know so that this interface can be removed.", error: false)] +internal interface IWorkCoordinatorPriorityService : ILanguageService +{ + /// + /// True if this document is less important than other documents in the project it is + /// contained in, and should have work scheduled for it happen after all other documents + /// in the project. + /// + Task IsLowPriorityAsync(Document document, CancellationToken cancellationToken); +} From 32dfeb4e8c27268cfbdcb424c33966629202b144 Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Wed, 13 Mar 2024 12:44:52 -0400 Subject: [PATCH 059/170] Address code review style comments --- .../Providers/AbstractDocCommentCompletionProvider.cs | 8 ++++++-- .../XmlDocCommentCompletionProvider.vb | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 3ee0db19310bc..63ffe67ded854 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -115,8 +115,12 @@ private CompletionItem GetItem(string name) protected IEnumerable GetAttributeItems(string tagName, ISet existingAttributes, bool addEqualsAndQuotes) { - return s_attributeMap.Where(x => x.elementName == tagName && !existingAttributes.Contains(x.attributeName)) - .Select(x => CreateCompletionItem(x.attributeName, beforeCaretText: addEqualsAndQuotes ? x.text : x.text[..^2], afterCaretText: addEqualsAndQuotes ? "\"" : "")); + return s_attributeMap + .Where(x => x.elementName == tagName && !existingAttributes.Contains(x.attributeName)) + .Select(x => CreateCompletionItem( + x.attributeName, + beforeCaretText: addEqualsAndQuotes ? x.text : x.text[..^2], + afterCaretText: addEqualsAndQuotes ? "\"" : "")); } protected IEnumerable GetAlwaysVisibleItems() diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 90fd3b9404964..3f81d4ed53984 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -332,7 +332,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Private Function GetAttributes(tagName As String, attributes As SyntaxList(Of XmlNodeSyntax)) As IEnumerable(Of CompletionItem) Dim existingAttributeNames = attributes.Select(AddressOf GetAttributeName).WhereNotNull().ToSet() - Return GetAttributeItems(tagName, existingAttributeNames, false) + Return GetAttributeItems(tagName, existingAttributeNames, addEqualsAndQuotes := false) End Function Private Shared Function GetAttributeName(node As XmlNodeSyntax) As String From bb16de62ce02e47d335bfaf0e2cd8b301f5c90f4 Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Wed, 13 Mar 2024 12:45:43 -0400 Subject: [PATCH 060/170] Move tests to CompletionCommandHandler and add equals/quotes if there's trivia --- ...mentationCommentCompletionProviderTests.cs | 17 ------- ...arpCompletionCommandHandlerTests_XmlDoc.vb | 48 +++++++++++++++++++ .../XmlDocCommentCompletionProvider.cs | 4 +- .../AbstractDocCommentCompletionProvider.cs | 7 +++ 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 94ca88f585996..09c7f1c4cc4dd 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -1044,23 +1044,6 @@ void Goo() { } await VerifyItemExistsAsync(text, "langword"); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/11489")] - public async Task AttributeValueOnQuote() - { - var text = """ - class C - { - /// - /// + Public Async Function InvokeWithOpenAngleSeeCommitLangwordWithEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task + + Using state = TestStateFactory.CreateCSharpTestState( + + /// + void goo() { } +} + ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendTypeChars("l") + state.SendInvokeCompletionList() + Await state.AssertSelectedCompletionItem(displayText:="langword") + state.SendReturn() + + ' /// + Await state.AssertLineTextAroundCaret(" /// + Public Async Function InvokeWithOpenAngleSeeCommitLangwordWithSpaceEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task + + Using state = TestStateFactory.CreateCSharpTestState( + + /// + void goo() { } +} + ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendTypeChars("l") + state.SendInvokeCompletionList() + Await state.AssertSelectedCompletionItem(displayText:="langword") + state.SendReturn() + + ' /// + Await state.AssertLineTextAroundCaret(" /// Public Function InvokeWithNullKeywordCommitSeeLangword(showCompletionInArgumentLists As Boolean) As Task Return InvokeWithKeywordCommitSeeLangword("null", showCompletionInArgumentLists) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index dd6028ec8a342..b950e664dfc13 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -85,7 +85,9 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition, if (IsAttributeNameContext(token, position, out var elementName, out var existingAttributes)) { - return GetAttributeItems(elementName, existingAttributes, !token.GetNextToken().IsKind(SyntaxKind.EqualsToken)); + var nextToken = token.GetNextToken(); + return GetAttributeItems(elementName, existingAttributes, + addEqualsAndQuotes: !nextToken.IsKind(SyntaxKind.EqualsToken) || nextToken.HasLeadingTrivia); } var wasTriggeredAfterSpace = trigger.Kind == CompletionTriggerKind.Insertion && trigger.Character == ' '; diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 63ffe67ded854..0351f0944b2cb 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -283,6 +283,13 @@ public override async Task GetChangeAsync(Document document, C var replacementText = beforeCaretText; var newPosition = replacementSpan.Start + beforeCaretText.Length; + if (text.Length > replacementSpan.End + 1 + && text[replacementSpan.End] == '=' + && text[replacementSpan.End + 1] == '"') + { + newPosition += 2; + } + if (commitChar.HasValue && !char.IsWhiteSpace(commitChar.Value) && commitChar.Value != replacementText[^1]) { // include the commit character From 35abb420e7006195d8696eaf478b020d76510e7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 13 Mar 2024 11:24:57 -0700 Subject: [PATCH 061/170] Do not report unchanged files in workspace-pull diagnostics --- .../AbstractPullDiagnosticHandler.cs | 29 ++++++++++++------- ...AbstractWorkspacePullDiagnosticsHandler.cs | 3 ++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index e3869cb792c71..7e43bed13a75c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -20,16 +20,17 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - internal abstract class AbstractDocumentPullDiagnosticHandler - : AbstractPullDiagnosticHandler, ITextDocumentIdentifierHandler + internal abstract class AbstractDocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractPullDiagnosticHandler( + diagnosticAnalyzerService, + diagnosticRefresher, + globalOptions), ITextDocumentIdentifierHandler where TDiagnosticsParams : IPartialResultParams { - public AbstractDocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) : base(diagnosticAnalyzerService, diagnosticRefresher, globalOptions) - { - } + protected sealed override bool IsWorkspacePullDiagnosticsHandler => false; public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); } @@ -80,6 +81,8 @@ protected AbstractPullDiagnosticHandler( GlobalOptions = globalOptions; } + protected abstract bool IsWorkspacePullDiagnosticsHandler { get; } + protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; /// @@ -187,8 +190,14 @@ await ComputeAndReportCurrentDiagnosticsAsync( // Nothing changed between the last request and this one. Report a (null-diagnostics, // same-result-id) response to the client as that means they should just preserve the current // diagnostics they have for this file. - var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); + // + // Note: if this is a workspace request, we can do nothing, as that will be interpreted by the + // client as nothing having been changed for that document. + if (!this.IsWorkspacePullDiagnosticsHandler) + { + var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; + progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index ce0f46cbc792e..70b041533b350 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -18,6 +18,7 @@ using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + internal abstract class AbstractWorkspacePullDiagnosticsHandler : AbstractPullDiagnosticHandler, IDisposable where TDiagnosticsParams : IPartialResultParams @@ -33,6 +34,8 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler private int _lspChanged = 0; + protected sealed override bool IsWorkspacePullDiagnosticsHandler => true; + protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, From 72396c069d0861142612e13b4ca8a24f30ae2c37 Mon Sep 17 00:00:00 2001 From: Stuart Lang Date: Wed, 13 Mar 2024 11:43:40 -0700 Subject: [PATCH 062/170] Remove redundant feature (#72525) --- docs/Language Feature Status.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index b7651f0632ca6..4fbf571e12e5b 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -12,7 +12,6 @@ efforts behind them. | ------- | ------ | ----- | --------- | -------- | --------- | --------- | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | | [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [Params Span\ + Stackalloc any array type](https://github.com/dotnet/csharplang/issues/1757) | [params-span](https://github.com/dotnet/roslyn/tree/features/params-span) | [In Progress](https://github.com/dotnet/roslyn/issues/57049) | [cston](https://github.com/cston) | TBD | | [jaredpar](https://github.com/jaredpar) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [In Progress](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | | [jcouv](https://github.com/jcouv) | | [Roles/Extensions](https://github.com/dotnet/csharplang/issues/5497) | [roles](https://github.com/dotnet/roslyn/tree/features/roles) | [In Progress](https://github.com/dotnet/roslyn/issues/66722) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [jjonescz](https://github.com/jjonescz) | | [MadsTorgersen](https://github.com/MadsTorgersen) | | [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [Merged into 17.9p1](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | From ac8e4377b82925ebb4c4994230b4a6617488ed7c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 13 Mar 2024 11:36:57 -0700 Subject: [PATCH 063/170] Update tests --- .../Diagnostics/PullDiagnosticTests.cs | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 418de519f20c1..63cb36498c2ad 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -1347,14 +1347,8 @@ public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagno var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Equal(3, results2.Length); - Assert.Null(results2[0].Diagnostics); - Assert.Null(results2[1].Diagnostics); - Assert.Null(results2[2].Diagnostics); - - Assert.Equal(results[0].ResultId, results2[0].ResultId); - Assert.Equal(results[1].ResultId, results2[1].ResultId); - Assert.Equal(results[2].ResultId, results2[2].ResultId); + // 'no changes' will be reported as an empty array. + Assert.Empty(results2); } [Theory, CombinatorialData] @@ -1657,19 +1651,14 @@ public class {|caret:|} { } var previousResultIds = CreateDiagnosticParamsFromPreviousReports(results); results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResultIds); AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - - // Verify the diagnostic result for A.cs is unchanged as A.cs does not reference CSProj2. - Assert.Null(results[0].Diagnostics); - Assert.Equal(previousResultIds[0].resultId, results[0].ResultId); - Assert.Null(results[1].Diagnostics); - Assert.Equal(previousResultIds[1].resultId, results[1].ResultId); + Assert.Equal(2, results.Length); + // Note: tehre will be no results for A.cs as it is unchanged and does not reference CSProj2. // Verify that the diagnostics result for B.cs reflects the change we made to it. - Assert.Empty(results[2].Diagnostics); - Assert.NotEqual(previousResultIds[2].resultId, results[2].ResultId); - Assert.Empty(results[3].Diagnostics); - Assert.NotEqual(previousResultIds[3].resultId, results[3].ResultId); + Assert.Empty(results[0].Diagnostics); + Assert.NotEqual(previousResultIds[2].resultId, results[0].ResultId); + Assert.Empty(results[1].Diagnostics); + Assert.NotEqual(previousResultIds[3].resultId, results[1].ResultId); } [Theory, CombinatorialData] @@ -1728,7 +1717,7 @@ public class {|caret:|} { } } [Theory, CombinatorialData] - public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedUnChanged(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceDiagnosticsWithDependentProjectReloadedUnchanged(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup1 = @"namespace M @@ -1774,14 +1763,10 @@ public class {|caret:|} { } results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResultIds); // Verify that since no actual changes have been made we report unchanged diagnostics. + // We get an empty array here as this is workspace diagnostics, and we do not report unchanged + // docs there for efficiency. AssertEx.NotNull(results); - Assert.Equal(4, results.Length); - - // Diagnostics should be unchanged as the referenced project was only unloaded / reloaded, but did not actually change. - Assert.Null(results[0].Diagnostics); - Assert.Equal(previousResultIds[0].resultId, results[0].ResultId); - Assert.Null(results[2].Diagnostics); - Assert.Equal(previousResultIds[2].resultId, results[2].ResultId); + Assert.Empty(results); } [Theory, CombinatorialData] @@ -1830,13 +1815,10 @@ class A : B { } results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: previousResults); // Verify that since no actual changes have been made we report unchanged diagnostics. + // We get an empty array here as this is workspace diagnostics, and we do not report unchanged + // docs there for efficiency. AssertEx.NotNull(results); - Assert.Equal(6, results.Length); - - // Diagnostics should be unchanged as a referenced project was unloaded and reloaded. Order should not matter. - Assert.Null(results[0].Diagnostics); - Assert.All(results, result => Assert.Null(result.Diagnostics)); - Assert.All(results, result => Assert.True(previousResultIds.Contains(result.ResultId))); + Assert.Empty(results); } [Theory, CombinatorialData] From ad9182d9fe06e1ad7310e772a8467c9cf41139a3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 13 Mar 2024 11:58:29 -0700 Subject: [PATCH 064/170] Change how we report htings --- .../AbstractPullDiagnosticHandler.cs | 19 +++++++---------- ...AbstractWorkspacePullDiagnosticsHandler.cs | 2 -- .../DocumentPullDiagnosticHandler.cs | 7 +++++-- .../PublicDocumentPullDiagnosticsHandler.cs | 9 +++++--- .../PublicWorkspacePullDiagnosticsHandler.cs | 21 +++++++------------ .../WorkspacePullDiagnosticHandler.cs | 10 +++++++-- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 7e43bed13a75c..a0d11561d04c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -30,8 +31,6 @@ internal abstract class AbstractDocumentPullDiagnosticHandler where TDiagnosticsParams : IPartialResultParams { - protected sealed override bool IsWorkspacePullDiagnosticsHandler => false; - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); } @@ -81,8 +80,6 @@ protected AbstractPullDiagnosticHandler( GlobalOptions = globalOptions; } - protected abstract bool IsWorkspacePullDiagnosticsHandler { get; } - protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; /// @@ -103,9 +100,11 @@ protected abstract ValueTask> GetOrderedDiagno protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[] diagnostics, string resultId); /// - /// Creates the appropriate LSP type to report unchanged diagnostics. + /// Creates the appropriate LSP type to report unchanged diagnostics. Can return to + /// indicate nothing should be reported. This should be done for workspace requests to avoiding sending a huge + /// amount of "nothing changed" responses for most files. /// - protected abstract TReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId); + protected abstract bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out TReport? report); /// /// Creates the appropriate LSP type to report a removed file. @@ -193,11 +192,9 @@ await ComputeAndReportCurrentDiagnosticsAsync( // // Note: if this is a workspace request, we can do nothing, as that will be interpreted by the // client as nothing having been changed for that document. - if (!this.IsWorkspacePullDiagnosticsHandler) - { - var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); - } + var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; + if (TryCreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId, out var report)) + progress.Report(report); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 70b041533b350..d39af98d94d7b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -34,8 +34,6 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler private int _lspChanged = 0; - protected sealed override bool IsWorkspacePullDiagnosticsHandler => true; - protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 3942f3a1d2b3e..e9559f60a0123 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -49,8 +49,11 @@ protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentif protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) => CreateReport(identifier, diagnostics: null, resultId: null); - protected override VSInternalDiagnosticReport[] CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) - => CreateReport(identifier, diagnostics: null, resultId); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index f6f9086d76436..3a22a8a8ce483 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -66,11 +66,14 @@ protected override DocumentDiagnosticPartialReport CreateRemovedReport(TextDocum Items = [], }); - protected override DocumentDiagnosticPartialReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) - => new(new RelatedUnchangedDocumentDiagnosticReport + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out DocumentDiagnosticPartialReport report) + { + report = new RelatedUnchangedDocumentDiagnosticReport { ResultId = resultId - }); + }; + return true; + } protected override DocumentDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index bf8905d91fbb3..6c2beb5517041 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -70,20 +70,13 @@ protected override WorkspaceDiagnosticPartialReport CreateRemovedReport(TextDocu ] }); - protected override WorkspaceDiagnosticPartialReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) - => new(new WorkspaceDiagnosticReport - { - Items = - [ - new WorkspaceUnchangedDocumentDiagnosticReport - { - Uri = identifier.Uri, - // The documents provided by workspace reports are never open, so we return null. - Version = null, - ResultId = resultId, - } - ] - }); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out WorkspaceDiagnosticPartialReport report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = default; + return false; + } protected override WorkspaceDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index f519b0a632cbd..bb8dc13086045 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; @@ -40,8 +41,13 @@ protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocume protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) => CreateReport(identifier, diagnostics: null, resultId: null); - protected override VSInternalWorkspaceDiagnosticReport[] CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) - => CreateReport(identifier, diagnostics: null, resultId); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; + } protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); From ed4c468751b7a262bf21af29c74ba7d45aecca67 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 13 Mar 2024 12:26:46 -0700 Subject: [PATCH 065/170] Always log processId and increase attach timeout --- .../Microsoft.CodeAnalysis.LanguageServer/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index ef7e08dc3842b..4a234a66ed469 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -57,6 +57,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation var logger = loggerFactory.CreateLogger(); + logger.Log(serverConfiguration.LaunchDebugger ? LogLevel.Critical : LogLevel.Trace, "Server started with process ID {processId}", Environment.ProcessId); if (serverConfiguration.LaunchDebugger) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -66,8 +67,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation } else { - var timeout = TimeSpan.FromMinutes(1); - logger.LogCritical($"Server started with process ID {Environment.ProcessId}"); + var timeout = TimeSpan.FromMinutes(2); logger.LogCritical($"Waiting {timeout:g} for a debugger to attach"); using var timeoutSource = new CancellationTokenSource(timeout); while (!Debugger.IsAttached && !timeoutSource.Token.IsCancellationRequested) From 48a38ef63643a920d176572b28557035ca656723 Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Wed, 13 Mar 2024 16:29:24 -0400 Subject: [PATCH 066/170] Address code review --- ...mentationCommentCompletionProviderTests.cs | 17 +++++++++++ ...arpCompletionCommandHandlerTests_XmlDoc.vb | 30 ++++++++++++++++--- ...sicCompletionCommandHandlerTests_XmlDoc.vb | 23 ++++++++++++++ .../XmlDocCommentCompletionProvider.vb | 12 ++++---- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 09c7f1c4cc4dd..94ca88f585996 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -1044,6 +1044,23 @@ void Goo() { } await VerifyItemExistsAsync(text, "langword"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/11489")] + public async Task AttributeValueOnQuote() + { + var text = """ + class C + { + /// + /// - Public Async Function InvokeWithOpenAngleSeeCommitLangwordWithEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task + Public Async Function InvokeWithOpenAngleSeeCommitSeeWithEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + + /// + void goo() { } +} + ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) + state.SendInvokeCompletionList() + state.AssertItemsInOrder({"!--", "![CDATA[", "inheritdoc", "see", "seealso"}) + state.SendTypeChars("see") + Await state.AssertSelectedCompletionItem(displayText:="see") + state.SendReturn() + + ' /// ="" + Await state.AssertLineTextAroundCaret(" /// =""""") + End Using + End Function + + + Public Async Function InvokeWithOpenAngleSeeCommitLangwordWithEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task Using state = TestStateFactory.CreateCSharpTestState( + ' /// Public Async Function InvokeWithOpenAngleSeeCommitLangwordWithSpaceEqualsQuotes(showCompletionInArgumentLists As Boolean) As Task - Using state = TestStateFactory.CreateCSharpTestState( + ' /// + Public Async Function InvokeWithOpenAngleSeeCommitSeeWithEqualsQuotes() As Task + Using state = TestStateFactory.CreateVisualBasicTestState( + + ''' + Sub Goo() + End Sub +End Class + ]]>) + + state.SendTypeChars("l") + Await state.AssertCompletionSession() + Await state.AssertSelectedCompletionItem(displayText:="langword") + state.SendReturn() + + ' ''' Public Async Function InvokeWithOpenAngleCommitSeeOnCloseAngle() As Task diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 3f81d4ed53984..2d13001e61eff 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -237,20 +237,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers If targetToken.IsChildToken(Function(n As XmlNameSyntax) n.LocalName) AndAlso targetToken.Parent Is tagNameSyntax Then ' Date: Wed, 13 Mar 2024 17:04:43 -0400 Subject: [PATCH 067/170] Fix formatting --- .../CompletionProviders/XmlDocCommentCompletionProvider.vb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 2d13001e61eff..ce0bc1a06f973 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -333,8 +333,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Private Function GetAttributes(token As SyntaxToken, tagName As String, attributes As SyntaxList(Of XmlNodeSyntax)) As IEnumerable(Of CompletionItem) Dim existingAttributeNames = attributes.Select(AddressOf GetAttributeName).WhereNotNull().ToSet() Dim nextToken = token.GetNextToken() - Return GetAttributeItems(tagName, existingAttributeNames, - addEqualsAndQuotes := Not nextToken.IsKind(SyntaxKind.EqualsToken) Or nextToken.HasLeadingTrivia) + Return GetAttributeItems(tagName, existingAttributeNames, addEqualsAndQuotes := Not nextToken.IsKind(SyntaxKind.EqualsToken) Or nextToken.HasLeadingTrivia) End Function Private Shared Function GetAttributeName(node As XmlNodeSyntax) As String From a0936644bfa98524f68966020415111239175008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 13 Mar 2024 15:15:20 -0600 Subject: [PATCH 068/170] Microsoft.VisualStudio.SDK (#72359) --- eng/Directory.Packages.props | 42 ++++++++++++++++----------------- eng/generate-vssdk-versions.csx | 25 ++++++++++++++++---- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 7c204fad33abc..44f9452dad04e 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -21,7 +21,7 @@ 8.0.0 2.4.1 2.1.0 - 17.9.43-preview-1 + 17.10.22-preview-1 - + @@ -88,34 +88,32 @@ - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + + - - - - - diff --git a/eng/generate-vssdk-versions.csx b/eng/generate-vssdk-versions.csx index ceacfcaa4fd8f..00418d83313e0 100644 --- a/eng/generate-vssdk-versions.csx +++ b/eng/generate-vssdk-versions.csx @@ -30,7 +30,7 @@ if (!File.Exists(vssdkPackageSpecPath)) var vssdkPackageSpec = XDocument.Parse(File.ReadAllText(vssdkPackageSpecPath)); -var properties = new List(); +var properties = new List<(string packageId, string version)>(); foreach (var node in vssdkPackageSpec.Descendants()) { @@ -44,6 +44,7 @@ foreach (var node in vssdkPackageSpec.Descendants()) if (!id.StartsWith("Microsoft.VisualStudio") && !id.StartsWith("Microsoft.ServiceHub") && + !id.StartsWith("Microsoft.Build") && id != "Newtonsoft.Json" && id != "StreamJsonRpc" && id != "Nerdbank.Streams") @@ -56,15 +57,31 @@ foreach (var node in vssdkPackageSpec.Descendants()) continue; } - properties.Add($""); + properties.Add((id, version)); } } properties.Sort(); -foreach (var property in properties) +var seenMsbuild = false; +foreach (var (id, version) in properties) { - Console.WriteLine(property); + if (!id.StartsWith("Microsoft.Build")) + { + Console.WriteLine($""); + } + else if (!seenMsbuild) + { + Console.WriteLine($$""" + + + + + + """); + + seenMsbuild = true; + } } return 0; From 10a82d0f232e572f713ee06b820542c2ec918009 Mon Sep 17 00:00:00 2001 From: sdelarosbil Date: Wed, 13 Mar 2024 17:35:31 -0400 Subject: [PATCH 069/170] Fix formatting --- .../CompletionProviders/XmlDocCommentCompletionProvider.vb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index ce0bc1a06f973..220fea7817f55 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -333,7 +333,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Private Function GetAttributes(token As SyntaxToken, tagName As String, attributes As SyntaxList(Of XmlNodeSyntax)) As IEnumerable(Of CompletionItem) Dim existingAttributeNames = attributes.Select(AddressOf GetAttributeName).WhereNotNull().ToSet() Dim nextToken = token.GetNextToken() - Return GetAttributeItems(tagName, existingAttributeNames, addEqualsAndQuotes := Not nextToken.IsKind(SyntaxKind.EqualsToken) Or nextToken.HasLeadingTrivia) + Return GetAttributeItems(tagName, existingAttributeNames, + addEqualsAndQuotes:=Not nextToken.IsKind(SyntaxKind.EqualsToken) Or nextToken.HasLeadingTrivia) End Function Private Shared Function GetAttributeName(node As XmlNodeSyntax) As String From f087922266f56cd8805602c099773e8b8e86ddde Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Wed, 13 Mar 2024 16:00:05 -0700 Subject: [PATCH 070/170] Exclude VS-related packages on non-Windows (#72470) * Conditionalize setup packages to build only on Windows Set static graph restore to true on non-windows to align with windows scripting. * Fix EOL * Nothing hard is ever easy --- eng/build.sh | 4 ++++ src/Setup/Directory.Build.props | 2 ++ 2 files changed, 6 insertions(+) diff --git a/eng/build.sh b/eng/build.sh index 976487bc9db3c..44a7f987d853a 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -78,6 +78,7 @@ prepare_machine=false warn_as_error=false properties="" source_build=false +restoreUseStaticGraphEvaluation=true solution_to_build="Compilers.slnf" args="" @@ -172,6 +173,8 @@ while [[ $# > 0 ]]; do # Arcade specifies /p:ArcadeBuildFromSource=true instead of --sourceBuild, but that's not developer friendly so we # have an alias. source_build=true + # RestoreUseStaticGraphEvaluation will cause prebuilts + restoreUseStaticGraphEvaluation=false ;; --solution) solution_to_build=$2 @@ -294,6 +297,7 @@ function BuildSolution { /p:Pack=$pack \ /p:Publish=$publish \ /p:RunAnalyzersDuringBuild=$run_analyzers \ + /p:RestoreUseStaticGraphEvaluation=$restoreUseStaticGraphEvaluation \ /p:BootstrapBuildPath="$bootstrap_dir" \ /p:ContinuousIntegrationBuild=$ci \ /p:TreatWarningsAsErrors=true \ diff --git a/src/Setup/Directory.Build.props b/src/Setup/Directory.Build.props index 6eef643958f56..50fecf188a03e 100644 --- a/src/Setup/Directory.Build.props +++ b/src/Setup/Directory.Build.props @@ -2,5 +2,7 @@ true + + true From c87268add3728c1bc0e4a1b1fb4ac936934e5ea8 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Wed, 13 Mar 2024 17:44:38 -0700 Subject: [PATCH 071/170] Remove ImmutableHashMap in favor of ImmutableDictionary (#72520) My understanding is that ImmutableHashMap precedes ImmutableDictionary and only exists because of that reason. These two data structures appear to have similar performance and functionality. There is no reason to have our own implementation if there isn't an advantage to doing so. --- .../Portable/Workspace/Solution/Project.cs | 21 +- .../Portable/Workspace/Solution/Solution.cs | 7 +- .../Core/CompilerExtensions.projitems | 2 - .../CompilerUtilities/ImmutableHashMap.cs | 1120 ----------------- .../ImmutableHashMapExtensions.cs | 58 - 5 files changed, 13 insertions(+), 1195 deletions(-) delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 35c2a92e943b8..1ead6e28e9b29 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -28,10 +27,10 @@ public partial class Project { private readonly Solution _solution; private readonly ProjectState _projectState; - private ImmutableHashMap _idToDocumentMap = []; - private ImmutableHashMap _idToSourceGeneratedDocumentMap = []; - private ImmutableHashMap _idToAdditionalDocumentMap = []; - private ImmutableHashMap _idToAnalyzerConfigDocumentMap = []; + private ImmutableDictionary _idToDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToSourceGeneratedDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToAdditionalDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToAnalyzerConfigDocumentMap = ImmutableDictionary.Empty; internal Project(Solution solution, ProjectState projectState) { @@ -236,19 +235,19 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) /// Get the document in this project with the specified document Id. /// public Document? GetDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToDocumentMap, documentId, s_tryCreateDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToDocumentMap, documentId, s_tryCreateDocumentFunction, this); /// /// Get the additional document in this project with the specified document Id. /// public TextDocument? GetAdditionalDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToAdditionalDocumentMap, documentId, s_tryCreateAdditionalDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToAdditionalDocumentMap, documentId, s_tryCreateAdditionalDocumentFunction, this); /// /// Get the analyzer config document in this project with the specified document Id. /// public AnalyzerConfigDocument? GetAnalyzerConfigDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToAnalyzerConfigDocumentMap, documentId, s_tryCreateAnalyzerConfigDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToAnalyzerConfigDocumentMap, documentId, s_tryCreateAnalyzerConfigDocumentFunction, this); /// /// Gets a document or a source generated document in this solution with the specified document ID. @@ -285,7 +284,7 @@ public async ValueTask> GetSourceGeneratedD // return an iterator to avoid eagerly allocating all the document instances return generatedDocumentStates.States.Values.Select(state => - ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)))!; + ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))); } internal async ValueTask> GetAllRegularAndSourceGeneratedDocumentsAsync(CancellationToken cancellationToken = default) @@ -318,7 +317,7 @@ internal async ValueTask> GetAllRegularAndSourceGeneratedD } internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGeneratedDocumentState state) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))!; + => ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)); /// /// Returns the for a source generated document that has already been generated and observed. @@ -349,7 +348,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera if (documentState == null) return null; - return ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, s_createSourceGeneratedDocumentFunction, (documentState, this)); + return ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, 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 e7d1aeca91fcf..cafe4e3d878c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -28,7 +27,7 @@ public partial class Solution private readonly SolutionCompilationState _compilationState; // Values for all these are created on demand. - private ImmutableHashMap _projectIdToProjectMap; + private ImmutableDictionary _projectIdToProjectMap; /// /// Result of calling . @@ -45,7 +44,7 @@ private Solution( SolutionCompilationState compilationState, AsyncLazy? cachedFrozenSolution = null) { - _projectIdToProjectMap = []; + _projectIdToProjectMap = ImmutableDictionary.Empty; _compilationState = compilationState; _cachedFrozenSolution = cachedFrozenSolution ?? @@ -141,7 +140,7 @@ public Workspace Workspace { if (this.ContainsProject(projectId)) { - return ImmutableHashMapExtensions.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this); + return ImmutableInterlocked.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this); } return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 320b61bf7b2f4..bbe77051ecf65 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -540,8 +540,6 @@ - - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs deleted file mode 100644 index fdc05e011a342..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs +++ /dev/null @@ -1,1120 +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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; -using Contract = System.Diagnostics.Contracts.Contract; - -namespace Roslyn.Collections.Immutable; - -/// -/// An immutable unordered hash map implementation. -/// -/// The type of the key. -/// The type of the value. -[DebuggerDisplay("Count = {Count}")] -[DebuggerTypeProxy(typeof(ImmutableHashMap<,>.DebuggerProxy))] -[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -internal sealed class ImmutableHashMap : IImmutableDictionary - where TKey : notnull -{ - private static readonly ImmutableHashMap s_emptySingleton = []; - - /// - /// The root node of the tree that stores this map. - /// - private readonly Bucket? _root; - - /// - /// The comparer used to sort keys in this map. - /// - private readonly IEqualityComparer _keyComparer; - - /// - /// The comparer used to detect equivalent values in this map. - /// - private readonly IEqualityComparer _valueComparer; - - /// - /// Initializes a new instance of the class. - /// - /// The root. - /// The comparer. - /// The value comparer. - private ImmutableHashMap(Bucket? root, IEqualityComparer comparer, IEqualityComparer valueComparer) - : this(comparer, valueComparer) - { - RoslynDebug.AssertNotNull(comparer); - RoslynDebug.AssertNotNull(valueComparer); - - _root = root; - } - - /// - /// Initializes a new instance of the class. - /// - /// The comparer. - /// The value comparer. - internal ImmutableHashMap(IEqualityComparer? comparer = null, IEqualityComparer? valueComparer = null) - { - _keyComparer = comparer ?? EqualityComparer.Default; - _valueComparer = valueComparer ?? EqualityComparer.Default; - } - - /// - /// Gets an empty map with default equality comparers. - /// - public static ImmutableHashMap Empty => s_emptySingleton; - - /// - /// See the interface. - /// - public ImmutableHashMap Clear() - => this.IsEmpty ? this : Empty.WithComparers(_keyComparer, _valueComparer); - - #region Public methods - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap Add(TKey key, TValue value) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - var vb = new ValueBucket(key, value, _keyComparer.GetHashCode(key)); - if (_root == null) - { - return this.Wrap(vb); - } - else - { - return this.Wrap(_root.Add(0, vb, _keyComparer, _valueComparer, false)); - } - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap AddRange(IEnumerable> pairs) - { - Requires.NotNull(pairs, "pairs"); - Contract.Ensures(Contract.Result>() != null); - - return this.AddRange(pairs, overwriteOnCollision: false, avoidToHashMap: false); - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap SetItem(TKey key, TValue value) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - Contract.Ensures(!Contract.Result>().IsEmpty); - var vb = new ValueBucket(key, value, _keyComparer.GetHashCode(key)); - if (_root == null) - { - return this.Wrap(vb); - } - else - { - return this.Wrap(_root.Add(0, vb, _keyComparer, _valueComparer, true)); - } - } - - /// - /// Applies a given set of key=value pairs to an immutable dictionary, replacing any conflicting keys in the resulting dictionary. - /// - /// The key=value pairs to set on the map. Any keys that conflict with existing keys will overwrite the previous values. - /// An immutable dictionary. - [Pure] - public ImmutableHashMap SetItems(IEnumerable> items) - { - Requires.NotNull(items, "items"); - Contract.Ensures(Contract.Result>() != null); - - return this.AddRange(items, overwriteOnCollision: true, avoidToHashMap: false); - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap Remove(TKey key) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - if (_root != null) - { - return this.Wrap(_root.Remove(_keyComparer.GetHashCode(key), key, _keyComparer)); - } - - return this; - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap RemoveRange(IEnumerable keys) - { - Requires.NotNull(keys, "keys"); - Contract.Ensures(Contract.Result>() != null); - var map = _root; - if (map != null) - { - foreach (var key in keys) - { - map = map.Remove(_keyComparer.GetHashCode(key), key, _keyComparer); - if (map == null) - { - break; - } - } - } - - return this.Wrap(map); - } - - /// - /// Returns a hash map that uses the specified key and value comparers and has the same contents as this map. - /// - /// The key comparer. A value of null results in using the default equality comparer for the type. - /// The value comparer. A value of null results in using the default equality comparer for the type. - /// The hash map with the new comparers. - /// - /// In the event that a change in the key equality comparer results in a key collision, an exception is thrown. - /// - [Pure] - public ImmutableHashMap WithComparers(IEqualityComparer keyComparer, IEqualityComparer valueComparer) - { - keyComparer ??= EqualityComparer.Default; - - valueComparer ??= EqualityComparer.Default; - - if (_keyComparer == keyComparer) - { - if (_valueComparer == valueComparer) - { - return this; - } - else - { - // When the key comparer is the same but the value comparer is different, we don't need a whole new tree - // because the structure of the tree does not depend on the value comparer. - // We just need a new root node to store the new value comparer. - return new ImmutableHashMap(_root, _keyComparer, valueComparer); - } - } - else - { - var set = new ImmutableHashMap(keyComparer, valueComparer); - set = set.AddRange(this, overwriteOnCollision: false, avoidToHashMap: true); - return set; - } - } - - /// - /// Returns a hash map that uses the specified key comparer and current value comparer and has the same contents as this map. - /// - /// The key comparer. A value of null results in using the default equality comparer for the type. - /// The hash map with the new comparers. - /// - /// In the event that a change in the key equality comparer results in a key collision, an exception is thrown. - /// - [Pure] - public ImmutableHashMap WithComparers(IEqualityComparer keyComparer) - => this.WithComparers(keyComparer, _valueComparer); - - /// - /// Determines whether the ImmutableSortedMap<TKey,TValue> - /// contains an element with the specified value. - /// - /// - /// The value to locate in the ImmutableSortedMap<TKey,TValue>. - /// The value can be null for reference types. - /// - /// - /// true if the ImmutableSortedMap<TKey,TValue> contains - /// an element with the specified value; otherwise, false. - /// - [Pure] - public bool ContainsValue(TValue value) - => this.Values.Contains(value, _valueComparer); - - #endregion - - #region IImmutableDictionary Members - - /// - /// Gets the number of elements in this collection. - /// - public int Count - { - get { return _root != null ? _root.Count : 0; } - } - - /// - /// Gets a value indicating whether this instance is empty. - /// - /// - /// true if this instance is empty; otherwise, false. - /// - public bool IsEmpty - { - get { return this.Count == 0; } - } - - /// - /// Gets the keys in the map. - /// - public IEnumerable Keys - { - get - { - if (_root == null) - { - yield break; - } - - var stack = new Stack>(); - stack.Push(_root.GetAll().GetEnumerator()); - while (stack.Count > 0) - { - var en = stack.Peek(); - if (en.MoveNext()) - { - if (en.Current is ValueBucket vb) - { - yield return vb.Key; - } - else - { - stack.Push(en.Current.GetAll().GetEnumerator()); - } - } - else - { - stack.Pop(); - } - } - } - } - - /// - /// Gets the values in the map. - /// - public IEnumerable Values - { -#pragma warning disable 618 - get { return this.GetValueBuckets().Select(vb => vb.Value); } -#pragma warning restore 618 - } - - /// - /// Gets the with the specified key. - /// - public TValue this[TKey key] - { - get - { - if (this.TryGetValue(key, out var value)) - { - return value; - } - - throw new KeyNotFoundException(); - } - } - - /// - /// Determines whether the specified key contains key. - /// - /// The key. - /// - /// true if the specified key contains key; otherwise, false. - /// - public bool ContainsKey(TKey key) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - return vb != null; - } - - return false; - } - - /// - /// Determines whether this map contains the specified key-value pair. - /// - /// The key value pair. - /// - /// true if this map contains the key-value pair; otherwise, false. - /// - public bool Contains(KeyValuePair keyValuePair) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(keyValuePair.Key), keyValuePair.Key, _keyComparer); - return vb != null && _valueComparer.Equals(vb.Value, keyValuePair.Value); - } - - return false; - } - - /// - /// See the interface. - /// -#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) -#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - if (vb != null) - { - value = vb.Value; - return true; - } - } - - value = default; - return false; - } - - /// - /// See the interface. - /// - public bool TryGetKey(TKey equalKey, out TKey actualKey) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(equalKey), equalKey, _keyComparer); - if (vb != null) - { - actualKey = vb.Key; - return true; - } - } - - actualKey = equalKey; - return false; - } - - #endregion - - #region IEnumerable> Members - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator> GetEnumerator() - => this.GetValueBuckets().Select(vb => new KeyValuePair(vb.Key, vb.Value)).GetEnumerator(); - - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - => this.GetEnumerator(); - - #endregion - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - var builder = new StringBuilder("ImmutableHashMap["); - var needComma = false; - foreach (var kv in this) - { - builder.Append(kv.Key); - builder.Append(':'); - builder.Append(kv.Value); - if (needComma) - { - builder.Append(','); - } - - needComma = true; - } - - builder.Append(']'); - return builder.ToString(); - } - - /// - /// Exchanges a key for the actual key instance found in this map. - /// - /// The key to search for. - /// Receives the equal key found in the map. - /// A value indicating whether an equal and existing key was found in the map. - internal bool TryExchangeKey(TKey key, [NotNullWhen(true)] out TKey? existingKey) - { - var vb = _root?.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - if (vb != null) - { - existingKey = vb.Key; - return true; - } - else - { - existingKey = default; - return false; - } - } - - /// - /// Attempts to discover an instance beneath some enumerable sequence - /// if one exists. - /// - /// The sequence that may have come from an immutable map. - /// Receives the concrete typed value if one can be found. - /// true if the cast was successful; false otherwise. - private static bool TryCastToImmutableMap(IEnumerable> sequence, [NotNullWhen(true)] out ImmutableHashMap? other) - { - other = sequence as ImmutableHashMap; - if (other != null) - { - return true; - } - - return false; - } - - private ImmutableHashMap Wrap(Bucket? root) - { - if (root == null) - { - return this.Clear(); - } - - if (_root != root) - { - return root.Count == 0 ? this.Clear() : new ImmutableHashMap(root, _keyComparer, _valueComparer); - } - - return this; - } - - /// - /// Bulk adds entries to the map. - /// - /// The entries to add. - /// true to allow the sequence to include duplicate keys and let the last one win; false to throw on collisions. - /// true when being called from ToHashMap to avoid StackOverflow. - [Pure] - private ImmutableHashMap AddRange(IEnumerable> pairs, bool overwriteOnCollision, bool avoidToHashMap) - { - RoslynDebug.AssertNotNull(pairs); - Contract.Ensures(Contract.Result>() != null); - - // Some optimizations may apply if we're an empty list. - if (this.IsEmpty && !avoidToHashMap) - { - // If the items being added actually come from an ImmutableHashMap - // then there is no value in reconstructing it. - if (TryCastToImmutableMap(pairs, out var other)) - { - return other.WithComparers(_keyComparer, _valueComparer); - } - } - - var map = this; - foreach (var pair in pairs) - { - map = overwriteOnCollision - ? map.SetItem(pair.Key, pair.Value) - : map.Add(pair.Key, pair.Value); - } - - return map; - } - - private IEnumerable GetValueBuckets() - { - if (_root == null) - { - yield break; - } - - var stack = new Stack>(); - stack.Push(_root.GetAll().GetEnumerator()); - while (stack.Count > 0) - { - var en = stack.Peek(); - if (en.MoveNext()) - { - if (en.Current is ValueBucket vb) - { - yield return vb; - } - else - { - stack.Push(en.Current.GetAll().GetEnumerator()); - } - } - else - { - stack.Pop(); - } - } - } - - private abstract class Bucket - { - internal abstract int Count { get; } - - internal abstract Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue); - internal abstract Bucket? Remove(int hash, TKey key, IEqualityComparer comparer); - internal abstract ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer); - internal abstract IEnumerable GetAll(); - } - - private abstract class ValueOrListBucket : Bucket - { - /// - /// The hash for this bucket. - /// - internal readonly int Hash; - - /// - /// Initializes a new instance of the class. - /// - /// The hash. - protected ValueOrListBucket(int hash) - => this.Hash = hash; - } - - private sealed class ValueBucket : ValueOrListBucket - { - internal readonly TKey Key; - internal readonly TValue Value; - - /// - /// Initializes a new instance of the class. - /// - /// The key. - /// The value. - /// The hashcode. - internal ValueBucket(TKey key, TValue value, int hashcode) - : base(hashcode) - { - this.Key = key; - this.Value = value; - } - - internal override int Count => 1; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - if (this.Hash == bucket.Hash) - { - if (comparer.Equals(this.Key, bucket.Key)) - { - // Overwrite of same key. If the value is the same as well, don't switch out the bucket. - if (valueComparer.Equals(this.Value, bucket.Value)) - { - return this; - } - else - { - if (overwriteExistingValue) - { - return bucket; - } - else - { - throw new ArgumentException(Strings.DuplicateKey); - } - } - } - else - { - // two of the same hash will never be happy in a hash bucket - return new ListBucket([this, bucket]); - } - } - else - { - return new HashBucket(suggestedHashRoll, this, bucket); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash && comparer.Equals(this.Key, key)) - { - return null; - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash && comparer.Equals(this.Key, key)) - { - return this; - } - - return null; - } - - internal override IEnumerable GetAll() - => SpecializedCollections.SingletonEnumerable(this); - } - - private sealed class ListBucket : ValueOrListBucket - { - private readonly ValueBucket[] _buckets; - - /// - /// Initializes a new instance of the class. - /// - /// The buckets. - internal ListBucket(ValueBucket[] buckets) - : base(buckets[0].Hash) - { - RoslynDebug.AssertNotNull(buckets); - Debug.Assert(buckets.Length >= 2); - _buckets = buckets; - } - - internal override int Count => _buckets.Length; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - if (this.Hash == bucket.Hash) - { - var pos = this.Find(bucket.Key, comparer); - if (pos >= 0) - { - // If the value hasn't changed for this key, return the original bucket. - if (valueComparer.Equals(bucket.Value, _buckets[pos].Value)) - { - return this; - } - else - { - if (overwriteExistingValue) - { - return new ListBucket(_buckets.ReplaceAt(pos, bucket)); - } - else - { - throw new ArgumentException(Strings.DuplicateKey); - } - } - } - else - { - return new ListBucket(_buckets.InsertAt(_buckets.Length, bucket)); - } - } - else - { - return new HashBucket(suggestedHashRoll, this, bucket); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash) - { - var pos = this.Find(key, comparer); - if (pos >= 0) - { - if (_buckets.Length == 1) - { - return null; - } - else if (_buckets.Length == 2) - { - return pos == 0 ? _buckets[1] : _buckets[0]; - } - else - { - return new ListBucket(_buckets.RemoveAt(pos)); - } - } - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash) - { - var pos = this.Find(key, comparer); - if (pos >= 0) - { - return _buckets[pos]; - } - } - - return null; - } - - private int Find(TKey key, IEqualityComparer comparer) - { - for (var i = 0; i < _buckets.Length; i++) - { - if (comparer.Equals(key, _buckets[i].Key)) - { - return i; - } - } - - return -1; - } - - internal override IEnumerable GetAll() - => _buckets; - } - - private sealed class HashBucket : Bucket - { - private readonly int _hashRoll; - private readonly uint _used; - private readonly Bucket[] _buckets; - private readonly int _count; - - /// - /// Initializes a new instance of the class. - /// - /// The hash roll. - /// The used. - /// The buckets. - /// The count. - private HashBucket(int hashRoll, uint used, Bucket[] buckets, int count) - { - RoslynDebug.AssertNotNull(buckets); - Debug.Assert(buckets.Length == CountBits(used)); - - _hashRoll = hashRoll & 31; - _used = used; - _buckets = buckets; - _count = count; - } - - /// - /// Initializes a new instance of the class. - /// - /// The suggested hash roll. - /// The bucket1. - /// The bucket2. - internal HashBucket(int suggestedHashRoll, ValueOrListBucket bucket1, ValueOrListBucket bucket2) - { - RoslynDebug.AssertNotNull(bucket1); - RoslynDebug.AssertNotNull(bucket2); - Debug.Assert(bucket1.Hash != bucket2.Hash); - - // find next hashRoll that causes these two to be slotted in different buckets - var h1 = bucket1.Hash; - var h2 = bucket2.Hash; - int s1; - int s2; - for (var i = 0; i < 32; i++) - { - _hashRoll = (suggestedHashRoll + i) & 31; - s1 = this.ComputeLogicalSlot(h1); - s2 = this.ComputeLogicalSlot(h2); - if (s1 != s2) - { - _count = 2; - _used = (1u << s1) | (1u << s2); - _buckets = new Bucket[2]; - _buckets[this.ComputePhysicalSlot(s1)] = bucket1; - _buckets[this.ComputePhysicalSlot(s2)] = bucket2; - return; - } - } - - throw new InvalidOperationException(); - } - - internal override int Count => _count; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer keyComparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - var logicalSlot = ComputeLogicalSlot(bucket.Hash); - if (IsInUse(logicalSlot)) - { - // if this slot is in use, then add the new item to the one in this slot - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var existing = _buckets[physicalSlot]; - - // suggest hash roll that will cause any nested hash bucket to use entirely new bits for picking logical slot - // note: we ignore passed in suggestion, and base new suggestion off current hash roll. - var added = existing.Add(_hashRoll + 5, bucket, keyComparer, valueComparer, overwriteExistingValue); - if (added != existing) - { - var newBuckets = _buckets.ReplaceAt(physicalSlot, added); - return new HashBucket(_hashRoll, _used, newBuckets, _count - existing.Count + added.Count); - } - else - { - return this; - } - } - else - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var newBuckets = _buckets.InsertAt(physicalSlot, bucket); - var newUsed = InsertBit(logicalSlot, _used); - return new HashBucket(_hashRoll, newUsed, newBuckets, _count + bucket.Count); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - var logicalSlot = ComputeLogicalSlot(hash); - if (IsInUse(logicalSlot)) - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var existing = _buckets[physicalSlot]; - var result = existing.Remove(hash, key, comparer); - if (result == null) - { - if (_buckets.Length == 1) - { - return null; - } - else if (_buckets.Length == 2) - { - return physicalSlot == 0 ? _buckets[1] : _buckets[0]; - } - else - { - return new HashBucket(_hashRoll, RemoveBit(logicalSlot, _used), _buckets.RemoveAt(physicalSlot), _count - existing.Count); - } - } - else if (_buckets[physicalSlot] != result) - { - return new HashBucket(_hashRoll, _used, _buckets.ReplaceAt(physicalSlot, result), _count - existing.Count + result.Count); - } - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - var logicalSlot = ComputeLogicalSlot(hash); - if (IsInUse(logicalSlot)) - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - return _buckets[physicalSlot].Get(hash, key, comparer); - } - - return null; - } - - internal override IEnumerable GetAll() - => _buckets; - - private bool IsInUse(int logicalSlot) - => ((1 << logicalSlot) & _used) != 0; - - private int ComputeLogicalSlot(int hc) - { - var uc = unchecked((uint)hc); - var rotated = RotateRight(uc, _hashRoll); - return unchecked((int)(rotated & 31)); - } - - [Pure] - private static uint RotateRight(uint v, int n) - { - Debug.Assert(n is >= 0 and < 32); - if (n == 0) - { - return v; - } - - return v >> n | (v << (32 - n)); - } - - private int ComputePhysicalSlot(int logicalSlot) - { - Debug.Assert(logicalSlot is >= 0 and < 32); - Contract.Ensures(Contract.Result() >= 0); - if (_buckets.Length == 32) - { - return logicalSlot; - } - - if (logicalSlot == 0) - { - return 0; - } - - var mask = uint.MaxValue >> (32 - logicalSlot); // only count the bits up to the logical slot # - return CountBits(_used & mask); - } - - [Pure] - private static int CountBits(uint v) - { - unchecked - { - v -= ((v >> 1) & 0x55555555u); - v = (v & 0x33333333u) + ((v >> 2) & 0x33333333u); - return (int)((v + (v >> 4) & 0xF0F0F0Fu) * 0x1010101u) >> 24; - } - } - - [Pure] - private static uint InsertBit(int position, uint bits) - { - Debug.Assert(0 == (bits & (1u << position))); - return bits | (1u << position); - } - - [Pure] - private static uint RemoveBit(int position, uint bits) - { - Debug.Assert(0 != (bits & (1u << position))); - return bits & ~(1u << position); - } - } - - #region IImmutableDictionary Members - - IImmutableDictionary IImmutableDictionary.Clear() - => this.Clear(); - - IImmutableDictionary IImmutableDictionary.Add(TKey key, TValue value) - => this.Add(key, value); - - IImmutableDictionary IImmutableDictionary.SetItem(TKey key, TValue value) - => this.SetItem(key, value); - - IImmutableDictionary IImmutableDictionary.SetItems(IEnumerable> items) - => this.SetItems(items); - - IImmutableDictionary IImmutableDictionary.AddRange(IEnumerable> pairs) - => this.AddRange(pairs); - - IImmutableDictionary IImmutableDictionary.RemoveRange(IEnumerable keys) - => this.RemoveRange(keys); - - IImmutableDictionary IImmutableDictionary.Remove(TKey key) - => this.Remove(key); - - #endregion - - /// - /// A simple view of the immutable collection that the debugger can show to the developer. - /// - private class DebuggerProxy - { - /// - /// The collection to be enumerated. - /// - private readonly ImmutableHashMap _map; - - /// - /// The simple view of the collection. - /// - private KeyValuePair[]? _contents; - - /// - /// Initializes a new instance of the class. - /// - /// The collection to display in the debugger - public DebuggerProxy(ImmutableHashMap map) - { - Requires.NotNull(map, "map"); - _map = map; - } - - /// - /// Gets a simple debugger-viewable collection. - /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Contents - { - get - { - _contents ??= _map.ToArray(); - - return _contents; - } - } - } - - private static class Requires - { - [DebuggerStepThrough] - public static T NotNullAllowStructs(T value, string parameterName) - { - if (value == null) - { - throw new ArgumentNullException(parameterName); - } - - return value; - } - - [DebuggerStepThrough] - public static T NotNull(T value, string parameterName) where T : class - { - if (value == null) - { - throw new ArgumentNullException(parameterName); - } - - return value; - } - - [DebuggerStepThrough] - public static Exception FailRange(string parameterName, string? message = null) - { - if (string.IsNullOrEmpty(message)) - { - throw new ArgumentOutOfRangeException(parameterName); - } - - throw new ArgumentOutOfRangeException(parameterName, message); - } - - [DebuggerStepThrough] - public static void Range(bool condition, string parameterName, string? message = null) - { - if (!condition) - { - Requires.FailRange(parameterName, message); - } - } - } - - private static class Strings - { - public static string DuplicateKey => CompilerExtensionsResources.An_element_with_the_same_key_but_a_different_value_already_exists; - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs deleted file mode 100644 index 6236cb881c727..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs +++ /dev/null @@ -1,58 +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 Roslyn.Collections.Immutable; - -namespace Roslyn.Utilities; - -internal static class ImmutableHashMapExtensions -{ - /// - /// Obtains the value for the specified key from a dictionary, or adds a new value to the dictionary where the key did not previously exist. - /// - /// The type of key stored by the dictionary. - /// The type of value stored by the dictionary. - /// The type of argument supplied to the value factory. - /// The variable or field to atomically update if the specified is not in the dictionary. - /// The key for the value to retrieve or add. - /// The function to execute to obtain the value to insert into the dictionary if the key is not found. Returns null if the value can't be obtained. - /// The argument to pass to the value factory. - /// The value obtained from the dictionary or if it was not present. - public static TValue? GetOrAdd(ref ImmutableHashMap location, TKey key, Func valueProvider, TArg factoryArgument) - where TKey : notnull - where TValue : class - { - Contract.ThrowIfNull(valueProvider); - - var map = Volatile.Read(ref location); - Contract.ThrowIfNull(map); - if (map.TryGetValue(key, out var existingValue)) - { - return existingValue; - } - - var newValue = valueProvider(key, factoryArgument); - if (newValue is null) - { - return null; - } - - do - { - var augmentedMap = map.Add(key, newValue); - var replacedMap = Interlocked.CompareExchange(ref location, augmentedMap, map); - if (replacedMap == map) - { - return newValue; - } - - map = replacedMap; - } - while (!map.TryGetValue(key, out existingValue)); - - return existingValue; - } -} From f520a73885f163f6b9f08b9c64eb0fa381ca60ed Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Wed, 13 Mar 2024 19:15:25 -0700 Subject: [PATCH 072/170] Reduce allocations in SynchronizeAssetsAsync (#72533) This method was allocating an ImmutableArray each time it would request asset synchronization. Instead, we can use a pooled checksum array. --- .../Core/Portable/Serialization/PooledList.cs | 7 ++-- .../Fakes/SimpleAssetSource.cs | 5 ++- .../Remote/Core/ISolutionAssetProvider.cs | 4 +- .../Core/RemoteHostAssetSerialization.cs | 6 ++- .../Remote/Core/SolutionAssetProvider.cs | 8 ++-- .../Remote/Core/SolutionAssetStorage.Scope.cs | 3 +- .../Remote/ServiceHub/Host/AssetProvider.cs | 42 +++++++++++++++---- .../Remote/ServiceHub/Host/IAssetSource.cs | 3 +- .../ServiceHub/Host/SolutionAssetCache.cs | 3 ++ .../ServiceHub/Host/SolutionAssetSource.cs | 3 +- 10 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs index b6bdcf1bf0522..672612efae0a6 100644 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ b/src/Workspaces/Core/Portable/Serialization/PooledList.cs @@ -4,9 +4,8 @@ #nullable disable +using System; using System.Collections.Generic; -using System.Collections.Immutable; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; @@ -15,12 +14,12 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal static class Creator { - public static PooledObject> CreateChecksumSet(ImmutableArray checksums) + public static PooledObject> CreateChecksumSet(ReadOnlyMemory checksums) { var items = SharedPools.Default>().GetPooledObject(); var hashSet = items.Object; - foreach (var checksum in checksums) + foreach (var checksum in checksums.Span) hashSet.Add(checksum); return items; diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index 5f4e31d523c6e..60dd23543517c 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.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.Generic; using System.Collections.Immutable; using System.IO; @@ -18,11 +19,11 @@ namespace Microsoft.CodeAnalysis.Remote.Testing; internal sealed class SimpleAssetSource(ISerializerService serializerService, IReadOnlyDictionary map) : IAssetSource { public ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ImmutableArray checksums, ISerializerService deserializerService, CancellationToken cancellationToken) + Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService deserializerService, CancellationToken cancellationToken) { var results = new List(); - foreach (var checksum in checksums) + foreach (var checksum in checksums.Span) { Contract.ThrowIfFalse(map.TryGetValue(checksum, out var data)); diff --git a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs index cf98ca3fcacec..b57bf95fcb2b2 100644 --- a/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/ISolutionAssetProvider.cs @@ -2,7 +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.Collections.Immutable; +using System; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; @@ -26,5 +26,5 @@ internal interface ISolutionAssetProvider /// save substantially on performance by avoiding having to search the full solution tree to find matching items for /// a particular checksum. ValueTask WriteAssetsAsync( - PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ImmutableArray checksums, CancellationToken cancellationToken); + PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 770150e2d5ea1..b7a04d068ce19 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.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.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -24,7 +25,7 @@ public static async ValueTask WriteDataAsync( ISerializerService serializer, SolutionReplicationContext context, Checksum solutionChecksum, - ImmutableArray checksums, + ReadOnlyMemory checksums, CancellationToken cancellationToken) { using var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken); @@ -40,8 +41,9 @@ public static async ValueTask WriteDataAsync( Debug.Assert(assetMap != null); - foreach (var checksum in checksums) + for (var i = 0; i < checksums.Span.Length; i++) { + var checksum = checksums.Span[i]; var asset = assetMap[checksum]; // We flush after each item as that forms a reasonably sized chunk of data to want to then send over the diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index f5952b44f6319..52a3f75670cc7 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -4,13 +4,11 @@ using System; using System.Buffers; -using System.Collections.Immutable; using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; @@ -31,7 +29,7 @@ public ValueTask WriteAssetsAsync( PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, - ImmutableArray checksums, + ReadOnlyMemory checksums, CancellationToken cancellationToken) { // Suppress ExecutionContext flow for asynchronous operations operate on the pipe. In addition to avoiding @@ -43,7 +41,7 @@ public ValueTask WriteAssetsAsync( using var _ = FlowControlHelper.TrySuppressFlow(); return WriteAssetsSuppressedFlowAsync(pipeWriter, solutionChecksum, assetHint, checksums, cancellationToken); - async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ImmutableArray checksums, CancellationToken cancellationToken) + async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) { // The responsibility is on us (as per the requirements of RemoteCallback.InvokeAsync) to Complete the // pipewriter. This will signal to streamjsonrpc that the writer passed into it is complete, which will @@ -68,7 +66,7 @@ private async ValueTask WriteAssetsWorkerAsync( PipeWriter pipeWriter, Checksum solutionChecksum, AssetHint assetHint, - ImmutableArray checksums, + ReadOnlyMemory checksums, CancellationToken cancellationToken) { var assetStorage = _services.GetRequiredService().AssetStorage; diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index a87a15de4cfcb..ce07e27e23cb1 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -47,7 +46,7 @@ public void Dispose() /// public async Task AddAssetsAsync( AssetHint assetHint, - ImmutableArray checksums, + ReadOnlyMemory checksums, Dictionary assetMap, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 4c0fe1193a253..75758a70003ab 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -21,6 +21,9 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { + private const int PooledChecksumArraySize = 256; + private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); + private readonly Checksum _solutionChecksum = solutionChecksum; private readonly ISerializerService _serializerService = serializerService; private readonly SolutionAssetCache _assetCache = assetCache; @@ -118,27 +121,50 @@ public async ValueTask SynchronizeAssetsAsync( using (Logger.LogBlock(FunctionId.AssetService_SynchronizeAssetsAsync, Checksum.GetChecksumsLogInfo, checksums, cancellationToken)) { - using var _1 = ArrayBuilder.GetInstance(checksums.Count, out var missingChecksums); + var missingChecksumsCount = 0; + + // Calculate the number of missing checksums upfront. Calculation is cheap and can help avoid extraneous allocations. + foreach (var checksum in checksums) + { + if (!_assetCache.ContainsAsset(checksum)) + missingChecksumsCount++; + } + + var usePool = missingChecksumsCount <= PooledChecksumArraySize; + var missingChecksums = usePool ? s_checksumPool.Allocate() : new Checksum[missingChecksumsCount]; + + missingChecksumsCount = 0; foreach (var checksum in checksums) { if (!_assetCache.TryGetAsset(checksum, out _)) - missingChecksums.Add(checksum); + { + missingChecksums[missingChecksumsCount] = checksum; + missingChecksumsCount++; + } } - var checksumsArray = missingChecksums.ToImmutableAndClear(); - var assets = await RequestAssetsAsync(assetHint, checksumsArray, cancellationToken).ConfigureAwait(false); + var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); + var assets = await RequestAssetsAsync(assetHint, missingChecksumsMemory, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(checksumsArray.Length != assets.Length); + Contract.ThrowIfTrue(missingChecksumsMemory.Length != assets.Length); for (int i = 0, n = assets.Length; i < n; i++) - _assetCache.GetOrAdd(checksumsArray[i], assets[i]); + _assetCache.GetOrAdd(missingChecksums[i], assets[i]); + + if (usePool) + s_checksumPool.Free(missingChecksums); } } private async Task> RequestAssetsAsync( - AssetHint assetHint, ImmutableArray checksums, CancellationToken cancellationToken) + AssetHint assetHint, ReadOnlyMemory checksums, CancellationToken cancellationToken) { - Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); +#if NETCOREAPP + Contract.ThrowIfTrue(checksums.Span.Contains(Checksum.Null)); +#else + Contract.ThrowIfTrue(checksums.Span.IndexOf(Checksum.Null) >= 0); +#endif + if (checksums.Length == 0) return []; diff --git a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs index cc8f9f1573087..840e043daa46a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.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 @@ namespace Microsoft.CodeAnalysis.Remote; internal interface IAssetSource { ValueTask> GetAssetsAsync( - Checksum solutionChecksum, AssetHint assetHint, ImmutableArray checksums, ISerializerService serializerService, CancellationToken cancellationToken); + Checksum solutionChecksum, AssetHint assetHint, ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index f41418215977f..c167b7162380b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -111,6 +111,9 @@ public bool TryGetAsset(Checksum checksum, [MaybeNullWhen(false)] out T value } } + public bool ContainsAsset(Checksum checksum) + => _assets.ContainsKey(checksum); + public void UpdateLastActivityTime() => _lastActivityTime = DateTime.UtcNow; diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 75b05acd026bb..4917033beae78 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.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; @@ -18,7 +19,7 @@ internal sealed class SolutionAssetSource(ServiceBrokerClient client) : IAssetSo public async ValueTask> GetAssetsAsync( Checksum solutionChecksum, AssetHint assetHint, - ImmutableArray checksums, + ReadOnlyMemory checksums, ISerializerService serializerService, CancellationToken cancellationToken) { From 26b809df38356d4fe1ee376f703dc8832a7ce461 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 14 Mar 2024 10:50:24 +0100 Subject: [PATCH 073/170] Check PublishData.json feeds (#72333) * Update BuildBoss docs * Check PublishData.json --- .../BuildBoss/CompilerNuGetCheckerUtil.cs | 42 ++++++++++++++++++- src/Tools/BuildBoss/ProjectCheckerUtil.cs | 2 +- src/Tools/BuildBoss/README.md | 8 ++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index c22b54c64f2d4..95268daf87b17 100644 --- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -15,6 +15,7 @@ using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; namespace BuildBoss { @@ -72,6 +73,7 @@ public bool Check(TextWriter textWriter) try { var allGood = true; + allGood &= CheckPublishData(textWriter); allGood &= CheckPackages(textWriter); allGood &= CheckExternalApis(textWriter); return allGood; @@ -83,6 +85,44 @@ public bool Check(TextWriter textWriter) } } + /// + /// Verify PublishData.json contains feeds for all packages that will be published. + /// + private bool CheckPublishData(TextWriter textWriter) + { + var allGood = true; + + // Load PublishData.json + var publishDataPath = Path.Combine(RepositoryDirectory, "eng", "config", "PublishData.json"); + var publishDataRoot = JObject.Parse(File.ReadAllText(publishDataPath)); + var publishDataPackages = publishDataRoot["packages"]["default"] as JObject; + + // Check all shipping packages have an entry in PublishData.json + var regex = new Regex(@"^(.*?)\.\d.*\.nupkg$"); + var packagesDirectory = Path.Combine(ArtifactsDirectory, "packages", Configuration, "Shipping"); + foreach (var packageFullPath in Directory.EnumerateFiles(packagesDirectory, "*.nupkg")) + { + var packageFileName = Path.GetFileName(packageFullPath); + var match = regex.Match(packageFileName); + if (!match.Success) + { + allGood = false; + textWriter.WriteLine($"Unexpected package file name '{packageFileName}'"); + } + else + { + var packageId = match.Groups[1].Value; + if (!publishDataPackages.ContainsKey(packageId)) + { + allGood = false; + textWriter.WriteLine($"Package doesn't have corresponding PublishData.json entry: {packageId} ({packageFileName})"); + } + } + } + + return allGood; + } + /// /// Verify the contents of the compiler packages match the expected input /// @@ -360,7 +400,7 @@ void verifyFolder(string folderRelativeName) } // As a simplification we only validate the assembly names that begin with Microsoft.CodeAnalysis. This is a good - // hueristic for finding assemblies that we build. Can be expanded in the future if we find more assemblies that + // heuristic for finding assemblies that we build. Can be expanded in the future if we find more assemblies that // are worth validating here. var neededDllNames = neededDllNameSet .Where(x => x.StartsWith("Microsoft.CodeAnalysis")) diff --git a/src/Tools/BuildBoss/ProjectCheckerUtil.cs b/src/Tools/BuildBoss/ProjectCheckerUtil.cs index 9c7555b2d8081..30169fa97809f 100644 --- a/src/Tools/BuildBoss/ProjectCheckerUtil.cs +++ b/src/Tools/BuildBoss/ProjectCheckerUtil.cs @@ -174,7 +174,7 @@ private bool CheckDeploymentSettings(TextWriter textWriter) /// /// It's important that every reference be included in the solution. MSBuild does not necessarily - /// apply all configuration entries to projects which are compiled via referenes but not included + /// apply all configuration entries to projects which are compiled via references but not included /// in the solution. /// private bool CheckProjectReferencesComplete(TextWriter textWriter, IEnumerable declaredReferences) diff --git a/src/Tools/BuildBoss/README.md b/src/Tools/BuildBoss/README.md index 7bea1ca910e33..c431f2575e9b8 100644 --- a/src/Tools/BuildBoss/README.md +++ b/src/Tools/BuildBoss/README.md @@ -6,7 +6,7 @@ This is a tool to validate the state of our solutions, project files and other b > BuildBoss.exe ``` -This tool is run on every CI job against our important solution files: [Roslyn.sln](https://github.com/dotnet/roslyn/blob/main/Roslyn.sln), and [CrossPlatform.sln](https://github.com/dotnet/roslyn/blob/main/CrossPlatform.sln). +This tool is run on every CI job against our important solution files: [Roslyn.sln](https://github.com/dotnet/roslyn/blob/main/Roslyn.sln). Violations reported are important to fix as they represent correctness issues in our build. Many of the properties verified represent problems that otherwise won't be verified at check in time. @@ -39,13 +39,13 @@ There are a number of properties which are simply unnecessary for build. They a ### Transitive references -Projects which represent full deployments must have a complete set of project references declared in the project file. Or in other words the declared set of project references much match the tranistive closure of project references. Any gap between the two sets won't be deployed on build which in turn will break F5, testing, etc ... +Projects which represent full deployments must have a complete set of project references declared in the project file. Or in other words the declared set of project references much match the transitive closure of project references. Any gap between the two sets won't be deployed on build which in turn will break F5, testing, etc ... ### Classifying projects Our build process depends on being able to correctly classify our projects: exe, VSIX, dll, etc ... This can typically be inferred from properties like OutputType. But in other occasions it requires a more declarative entry via the `` property. The tool will catch places where projects are incorrectly classified. -This could be done using MSBuild targets but the logic is hard to follow and complicates the build. It's easier and more readable to have a declaritive entry in the file. +This could be done using MSBuild targets but the logic is hard to follow and complicates the build. It's easier and more readable to have a declarative entry in the file. ## Solution Content Verified @@ -57,7 +57,7 @@ This is best demonstrated by example. Consider the following setup: - Project App.csproj produces App.exe and references Util.csproj - Solution App.sln includes App.csproj only -Now consider when App.sln is build with the following command line: +Now consider when App.sln is built with the following command line: ``` cmd msbuild /p:Configuration=Release App.sln From 224f1a3005fbd209aba051c855ecf23305094c6c Mon Sep 17 00:00:00 2001 From: andrei-ungureanu-uipath <61829128+andrei-ungureanu-uipath@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:57:52 +0200 Subject: [PATCH 074/170] Fix InvalidCastException when initializing the properties of an undeclared class inside a lambda function (#72480) Fixes https://github.com/dotnet/roslyn/issues/72456. Building the following snippet in a VB project breaks the compilation: ```vb Imports System Imports System.Linq.Expressions Module Program Public Function CreateExpression() As Expression(Of Func(Of Object)) Return Function() (New UndeclaredClass() With {.Name = "testName"}) End Function End Module ``` There is a difference between `VisualBasic` and `CSharp` when getting diagnostics for the snippet above. The latter checks for the initializer type while the former forces a `DirectCast` which throws an `InvalidCastException`. This PR checks the type before forcing the cast similar to the `CSharp` approach. ### Expected result `Error BC30002: Type 'UndeclaredClass' is not defined.` ### Actual result Compilation breaks with the following trace: ``` System.InvalidCastException HResult=0x80004002 Message=Unable to cast object of type 'Microsoft.CodeAnalysis.VisualBasic.BoundLiteral' to type 'Microsoft.CodeAnalysis.VisualBasic.BoundAssignmentOperator'. This exception was originally thrown at this call stack: Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.DiagnosticsPass.VisitObjectInitializerExpression(Microsoft.CodeAnalysis.VisualBasic.BoundObjectInitializerExpression node) Line 126 Basic Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.BoundObjectInitializerExpression.Accept(Microsoft.CodeAnalysis.VisualBasic.BoundTreeVisitor visitor) Line 3792 Basic Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.BoundTreeWalkerWithStackGuard.VisitExpressionWithoutStackGuard(Microsoft.CodeAnalysis.VisualBasic.BoundExpression node) Line 61 Basic Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.BoundTreeVisitor.VisitExpressionWithStackGuard(Integer recursionDepth, Microsoft.CodeAnalysis.VisualBasic.BoundExpression node) Line 186 Basic Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.BoundTreeWalkerWithStackGuard.Visit(Microsoft.CodeAnalysis.VisualBasic.BoundNode node) Line 50 Basic Microsoft.CodeAnalysis.VisualBasic.dll!Microsoft.CodeAnalysis.VisualBasic.DiagnosticsPass.Visit(Microsoft.CodeAnalysis.VisualBasic.BoundNode node) Line 208 Basic ... ``` --- .../DiagnosticsPass_ExpressionLambdas.vb | 19 ++++++++------ .../Emit/ExpressionTrees/CodeGenExprLambda.vb | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/Diagnostics/DiagnosticsPass_ExpressionLambdas.vb b/src/Compilers/VisualBasic/Portable/Lowering/Diagnostics/DiagnosticsPass_ExpressionLambdas.vb index cc1ca0a3d268c..cc44e74774a88 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/Diagnostics/DiagnosticsPass_ExpressionLambdas.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/Diagnostics/DiagnosticsPass_ExpressionLambdas.vb @@ -122,16 +122,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic For Each initializer In node.Initializers ' Ignore assignments in object initializers, only reference the value - Debug.Assert(initializer.Kind = BoundKind.AssignmentOperator) - Dim assignment = DirectCast(initializer, BoundAssignmentOperator) - Debug.Assert(assignment.LeftOnTheRightOpt Is Nothing) - - Dim propertyAccess = TryCast(assignment.Left, BoundPropertyAccess) - If propertyAccess IsNot Nothing Then - CheckRefReturningPropertyAccess(propertyAccess) + Dim boundExpression As BoundExpression = initializer + If boundExpression.Kind = BoundKind.AssignmentOperator Then + Dim assignment = DirectCast(initializer, BoundAssignmentOperator) + Debug.Assert(assignment.LeftOnTheRightOpt Is Nothing) + + boundExpression = assignment.Right + Dim propertyAccess = TryCast(assignment.Left, BoundPropertyAccess) + If propertyAccess IsNot Nothing Then + CheckRefReturningPropertyAccess(propertyAccess) + End If End If - Me.Visit(assignment.Right) + Me.Visit(boundExpression) Next Me._expressionTreePlaceholders.Remove(placeholder) diff --git a/src/Compilers/VisualBasic/Test/Emit/ExpressionTrees/CodeGenExprLambda.vb b/src/Compilers/VisualBasic/Test/Emit/ExpressionTrees/CodeGenExprLambda.vb index 9b2bb516786c7..f7d0a425cadfe 100644 --- a/src/Compilers/VisualBasic/Test/Emit/ExpressionTrees/CodeGenExprLambda.vb +++ b/src/Compilers/VisualBasic/Test/Emit/ExpressionTrees/CodeGenExprLambda.vb @@ -2015,6 +2015,31 @@ End Module CompileAndVerify(source, references:={Net40.SystemCore}).VerifyDiagnostics() End Sub + + Public Sub UndeclaredClassInLambdaFunction() + Dim source = + + + + + + Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime(source, {LinqAssemblyRef}) + AssertTheseDiagnostics(compilation.GetDiagnostics(), + +BC30002: Type 'UndeclaredClass' is not defined. + Return Function() (New UndeclaredClass() With {.Name = "testName"}) + ~~~~~~~~~~~~~~~ +) + End Sub + Public Sub Bug577271() From 0862838a8bd02d6eb6901968feb76f94e10305e6 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Thu, 14 Mar 2024 16:32:10 -0700 Subject: [PATCH 075/170] Add explicit package reference to System.Threading.Tasks.Dataflow in Microsoft.CodeAnalysis.InteractiveHost to ensure it's included for binding redirect --- .../Host/Microsoft.CodeAnalysis.InteractiveHost.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj index c4cd1fa0ad9ae..d59e3214b13ef 100644 --- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj +++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj @@ -68,6 +68,7 @@ + From 3b5b0d90eb507e6d25a978a08605f693e9d022e7 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Thu, 14 Mar 2024 16:57:13 -0700 Subject: [PATCH 076/170] Add comment --- .../Host/Microsoft.CodeAnalysis.InteractiveHost.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj index d59e3214b13ef..002cd61d995fe 100644 --- a/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj +++ b/src/Interactive/Host/Microsoft.CodeAnalysis.InteractiveHost.csproj @@ -68,6 +68,9 @@ + From b77e47048af837a8c67dcdc0a4aaf1fd907cb76e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Mar 2024 17:52:21 -0700 Subject: [PATCH 077/170] Fix tests --- .../Diagnostics/AdditionalFileDiagnosticsTests.cs | 8 ++------ .../Diagnostics/WorkspaceProjectDiagnosticsTests.cs | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs index 5101f94f107fc..3b4e20e082b7d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs @@ -47,10 +47,7 @@ public async Task TestWorkspaceDiagnosticsReportsAdditionalFileDiagnostic(bool u // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Null(results2[0].Diagnostics); - Assert.Null(results2[1].Diagnostics); - Assert.Equal(results[1].ResultId, results2[1].ResultId); - Assert.Null(results2[2].Diagnostics); + Assert.Empty(results2); } [Theory, CombinatorialData] @@ -121,8 +118,7 @@ public async Task TestWorkspaceDiagnosticsWithAdditionalFileInMultipleProjects(b // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Equal(results[1].ResultId, results2[1].ResultId); - Assert.Equal(results[4].ResultId, results2[4].ResultId); + Assert.Empty(results2); } protected override TestComposition Composition => base.Composition.AddParts(typeof(MockAdditionalFileDiagnosticAnalyzer)); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs index 2a744e63008ca..fd7f64bb4e005 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs @@ -37,9 +37,7 @@ public async Task TestWorkspaceDiagnosticsReportsProjectDiagnostic(bool useVSDia // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); - Assert.Null(results2[0].Diagnostics); - Assert.Null(results2[1].Diagnostics); - Assert.Equal(results[1].ResultId, results2[1].ResultId); + Assert.Empty(results2); } [Theory, CombinatorialData] From 6f42d5db3e8794a074748e42b1ba6304d47cc606 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Mar 2024 18:07:42 -0700 Subject: [PATCH 078/170] Have pull-diagnostics respect the flag to to not run during perf runs --- ...acySolutionEventsWorkspaceEventListener.cs | 4 +- .../AbstractPullDiagnosticHandler.cs | 131 ++++++++++-------- 2 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs index 9c34bcc14bbca..12535d5b92067 100644 --- a/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs +++ b/src/EditorFeatures/Core/SolutionEvents/HostLegacySolutionEventsWorkspaceEventListener.cs @@ -49,8 +49,8 @@ public HostLegacySolutionEventsWorkspaceEventListener( public void StartListening(Workspace workspace, object? serviceOpt) { - // We only support this option to disable crawling in internal perf runs to lower noise. It is not exposed to - // the user. + // 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; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index e3869cb792c71..6880b9ff808bd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Features.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CommonLanguageServerProtocol.Framework; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -130,80 +131,90 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT public async Task HandleRequestAsync( TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - var clientCapabilities = context.GetRequiredClientCapabilities(); - var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; - var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; - var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; - context.TraceInformation($"{handlerName} started getting diagnostics"); - - var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); - // The progress object we will stream reports to. using var progress = BufferedProgress.Create(diagnosticsParams.PartialResultToken); - // Get the set of results the request said were previously reported. We can use this to determine both - // what to skip, and what files we have to tell the client have been removed. - var previousResults = GetPreviousResults(diagnosticsParams) ?? []; - context.TraceInformation($"previousResults.Length={previousResults.Length}"); + // 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 (!this.GlobalOptions.GetOption(SolutionCrawlerRegistrationService.EnableSolutionCrawler)) + { + context.TraceInformation($"{this.GetType()}. Skipping due to {nameof(SolutionCrawlerRegistrationService.EnableSolutionCrawler)}={false}"); + } + else + { + var clientCapabilities = context.GetRequiredClientCapabilities(); + var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; + var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; + var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; + context.TraceInformation($"{handlerName} started getting diagnostics"); + + var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); - // Create a mapping from documents to the previous results the client says it has for them. That way as we - // process documents we know if we should tell the client it should stay the same, or we can tell it what - // the updated diagnostics are. - var documentToPreviousDiagnosticParams = GetIdToPreviousDiagnosticParams(context, previousResults, out var removedResults); + // Get the set of results the request said were previously reported. We can use this to determine both + // what to skip, and what files we have to tell the client have been removed. + var previousResults = GetPreviousResults(diagnosticsParams) ?? []; + context.TraceInformation($"previousResults.Length={previousResults.Length}"); - // First, let the client know if any workspace documents have gone away. That way it can remove those for - // the user from squiggles or error-list. - HandleRemovedDocuments(context, removedResults, progress); + // Create a mapping from documents to the previous results the client says it has for them. That way as we + // process documents we know if we should tell the client it should stay the same, or we can tell it what + // the updated diagnostics are. + var documentToPreviousDiagnosticParams = GetIdToPreviousDiagnosticParams(context, previousResults, out var removedResults); - // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the - // last time we notified the client. Report back either to the client so they can update accordingly. - var orderedSources = await GetOrderedDiagnosticSourcesAsync( - diagnosticsParams, context, cancellationToken).ConfigureAwait(false); + // First, let the client know if any workspace documents have gone away. That way it can remove those for + // the user from squiggles or error-list. + HandleRemovedDocuments(context, removedResults, progress); - context.TraceInformation($"Processing {orderedSources.Length} documents"); + // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the + // last time we notified the client. Report back either to the client so they can update accordingly. + var orderedSources = await GetOrderedDiagnosticSourcesAsync( + diagnosticsParams, context, cancellationToken).ConfigureAwait(false); - foreach (var diagnosticSource in orderedSources) - { - var globalStateVersion = _diagnosticRefresher.GlobalStateVersion; - - var project = diagnosticSource.GetProject(); - - var newResultId = await versionedCache.GetNewResultIdAsync( - documentToPreviousDiagnosticParams, - diagnosticSource.GetId(), - project, - computeCheapVersionAsync: async () => (globalStateVersion, await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false)), - computeExpensiveVersionAsync: async () => (globalStateVersion, await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false)), - cancellationToken).ConfigureAwait(false); - if (newResultId != null) - { - await ComputeAndReportCurrentDiagnosticsAsync( - context, diagnosticSource, progress, newResultId, clientCapabilities, cancellationToken).ConfigureAwait(false); - } - else + context.TraceInformation($"Processing {orderedSources.Length} documents"); + + foreach (var diagnosticSource in orderedSources) { - context.TraceInformation($"Diagnostics were unchanged for {diagnosticSource.ToDisplayString()}"); + var globalStateVersion = _diagnosticRefresher.GlobalStateVersion; + + var project = diagnosticSource.GetProject(); + + var newResultId = await versionedCache.GetNewResultIdAsync( + documentToPreviousDiagnosticParams, + diagnosticSource.GetId(), + project, + computeCheapVersionAsync: async () => (globalStateVersion, await project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false)), + computeExpensiveVersionAsync: async () => (globalStateVersion, await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false)), + cancellationToken).ConfigureAwait(false); + if (newResultId != null) + { + await ComputeAndReportCurrentDiagnosticsAsync( + context, diagnosticSource, progress, newResultId, clientCapabilities, cancellationToken).ConfigureAwait(false); + } + else + { + context.TraceInformation($"Diagnostics were unchanged for {diagnosticSource.ToDisplayString()}"); - // Nothing changed between the last request and this one. Report a (null-diagnostics, - // same-result-id) response to the client as that means they should just preserve the current - // diagnostics they have for this file. - var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); + // Nothing changed between the last request and this one. Report a (null-diagnostics, + // same-result-id) response to the client as that means they should just preserve the current + // diagnostics they have for this file. + var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; + progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); + } } - } - // Clear out the solution context to avoid retaining memory - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058 - context.ClearSolutionContext(); + // Clear out the solution context to avoid retaining memory + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058 + context.ClearSolutionContext(); - // Some implementations of the spec will re-open requests as soon as we close them, spamming the server. - // In those cases, we wait for the implementation to indicate that changes have occurred, then we close the connection - // so that the client asks us again. - await WaitForChangesAsync(context, cancellationToken).ConfigureAwait(false); + // Some implementations of the spec will re-open requests as soon as we close them, spamming the server. + // In those cases, we wait for the implementation to indicate that changes have occurred, then we close the connection + // so that the client asks us again. + await WaitForChangesAsync(context, cancellationToken).ConfigureAwait(false); + + // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been + // collecting and return that. + context.TraceInformation($"{this.GetType()} finished getting diagnostics"); + } - // If we had a progress object, then we will have been reporting to that. Otherwise, take what we've been - // collecting and return that. - context.TraceInformation($"{this.GetType()} finished getting diagnostics"); return CreateReturn(progress); } From 616fc283e903e534fc878538ea8bca99b41fd459 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Mar 2024 19:20:29 -0700 Subject: [PATCH 079/170] Remove stale test --- .../DiagnosticAnalyzerServiceTests.cs | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index a366b436a54c9..ec4590845f359 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -942,81 +942,6 @@ void M() } } - [Theory, CombinatorialData] - internal async Task TestCancellationDuringDiagnosticComputation_InProc(AnalyzerRegisterActionKind actionKind) - { - // This test verifies that we do no attempt to re-use CompilationWithAnalyzers instance in IDE in-proc diagnostic computation in presence of an OperationCanceledException during analysis. - // Attempting to do so has led to large number of reliability issues and flakiness in diagnostic computation, which we want to avoid. - - var source = @" -class A -{ - void M() - { - int x = 0; - } -}"; - - using var workspace = EditorTestWorkspace.CreateCSharp(source, - composition: s_editorFeaturesCompositionWithMockDiagnosticUpdateSourceRegistrationService.AddParts(typeof(TestDocumentTrackingService))); - Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions( - workspace.CurrentSolution.Options.WithChangedOption(new OptionKey(DiagnosticOptionsStorage.PullDiagnosticsFeatureFlag), false)))); - - var analyzer = new CancellationTestAnalyzer(actionKind); - var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer)); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); - - var project = workspace.CurrentSolution.Projects.Single(); - var document = project.Documents.Single(); - - Assert.IsType(workspace.GetService()); - var service = Assert.IsType(workspace.GetService()); - var globalOptions = workspace.GetService(); - - DiagnosticData diagnostic = null; - service.DiagnosticsUpdated += (s, eCollection) => - { - foreach (var e in eCollection) - { - var diagnostics = e.Diagnostics; - if (diagnostics.IsEmpty) - { - continue; - } - - Assert.Null(diagnostic); - diagnostic = Assert.Single(diagnostics); - } - }; - - var incrementalAnalyzer = service.CreateIncrementalAnalyzer(workspace); - - OpenDocumentAndMakeActive(document, workspace); - - // First invoke analysis with cancellation token, and verify canceled compilation and no reported diagnostics. - Assert.Empty(analyzer.CanceledCompilations); - try - { - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, analyzer.CancellationToken); - - throw ExceptionUtilities.Unreachable(); - } - catch (OperationCanceledException ex) when (ex.CancellationToken == analyzer.CancellationToken) - { - } - - Assert.Single(analyzer.CanceledCompilations); - Assert.Null(diagnostic); - - // Then invoke analysis without cancellation token, and verify non-cancelled diagnostic. - await incrementalAnalyzer.ForceAnalyzeProjectAsync(project, CancellationToken.None); - - await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - - Assert.True(diagnostic != null); - Assert.Equal(CancellationTestAnalyzer.DiagnosticId, diagnostic.Id); - } - [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/49698")] internal async Task TestOnlyRequiredAnalyzerExecutedDuringDiagnosticComputation(bool documentAnalysis) From a615340582ef9f68734c877b78d37730736f3404 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:14:56 +0100 Subject: [PATCH 080/170] Update dependencies from https://github.com/dotnet/source-build-reference-packages build 20240311.2 (#72502) Microsoft.SourceBuild.Intermediate.source-build-reference-packages From Version 9.0.0-alpha.1.24155.1 -> To Version 9.0.0-alpha.1.24161.2 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d281fce2ac599..ee8c8d0b23530 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -8,9 +8,9 @@ - + https://github.com/dotnet/source-build-reference-packages - 768378e775fc5ddc99d41f2c4d1c78182f326ea7 + 8d6e9cf10f64ff8fc02e434b516f6ca87c4b7215 From ff27b0876a5b38d1bcc503ae763f81e5318ff18a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:16:08 +0100 Subject: [PATCH 081/170] Update dependencies from https://github.com/dotnet/arcade build 20240311.1 (#72503) Microsoft.SourceBuild.Intermediate.arcade , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24158.4 -> To Version 8.0.0-beta.24161.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 12 ++++++------ global.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ee8c8d0b23530..fbe4b4f6b8f44 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -110,14 +110,14 @@ - + https://github.com/dotnet/arcade - 052a4b9e7a9bdb9744c86c05665f1b46e4d59b15 + 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 - + https://github.com/dotnet/arcade - 052a4b9e7a9bdb9744c86c05665f1b46e4d59b15 + 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 @@ -144,9 +144,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 052a4b9e7a9bdb9744c86c05665f1b46e4d59b15 + 5c3fdd3b5aaaa32b24383ec12a60b37ebff13079 https://github.com/dotnet/roslyn-analyzers diff --git a/global.json b/global.json index ff6f775d56486..f323d2b0bb65e 100644 --- a/global.json +++ b/global.json @@ -12,8 +12,8 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24158.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24158.4", + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24161.1", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24161.1", "Microsoft.Build.Traversal": "3.4.0" } } From dea4c7f73929154b5d2f43782417f343e1e00dbb Mon Sep 17 00:00:00 2001 From: Maryam Ariyan Date: Fri, 15 Mar 2024 09:20:32 -0700 Subject: [PATCH 082/170] Fix failing tests --- .../TelemetryReporterTests.cs | 2 +- .../Utilities/AbstractLanguageServerHostTests.cs | 3 ++- .../Utilities/LanguageServerTestComposition.cs | 4 ++-- .../WorkspaceProjectFactoryServiceTests.cs | 6 +++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs index b23ed5d2a4d0f..25f175456269d 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/TelemetryReporterTests.cs @@ -18,7 +18,7 @@ public TelemetryReporterTests(ITestOutputHelper testOutputHelper) private async Task CreateReporterAsync() { - var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync(TestOutputLogger.Factory, includeDevKitComponents: true); + var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync(TestOutputLogger.Factory, includeDevKitComponents: true, out var _); var reporter = exportProvider.GetExport().Value; Assert.NotNull(reporter); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs index 628c98933de55..49c5e48bb623f 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs @@ -33,7 +33,8 @@ protected sealed class TestLspServer : IAsyncDisposable internal static async Task CreateAsync(ClientCapabilities clientCapabilities, TestOutputLogger logger, bool includeDevKitComponents = true) { - var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync(logger.Factory, includeDevKitComponents); + var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( + logger.Factory, includeDevKitComponents, out var _); var testLspServer = new TestLspServer(exportProvider, logger); var initializeResponse = await testLspServer.ExecuteRequestAsync(Methods.InitializeName, new InitializeParams { Capabilities = clientCapabilities }, CancellationToken.None); Assert.NotNull(initializeResponse?.Capabilities); diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs index fab6370f9f30d..785846c6cbd4b 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs @@ -22,10 +22,10 @@ internal sealed class LanguageServerTestComposition private static string GetDevKitExtensionPath() => Path.Combine(AppContext.BaseDirectory, DevKitExtensionSubdirectory, DevKitAssemblyFileName); - public static Task CreateExportProviderAsync(ILoggerFactory loggerFactory, bool includeDevKitComponents) + public static Task CreateExportProviderAsync(ILoggerFactory loggerFactory, bool includeDevKitComponents, out ServerConfiguration serverConfiguration) { var devKitDependencyPath = includeDevKitComponents ? GetDevKitExtensionPath() : null; - var serverConfiguration = new ServerConfiguration(LaunchDebugger: false, + serverConfiguration = new ServerConfiguration(LaunchDebugger: false, MinimumLogLevel: LogLevel.Trace, StarredCompletionsPath: null, TelemetryLevel: null, diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs index 2b5aac043fcc2..5a896b036b216 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/WorkspaceProjectFactoryServiceTests.cs @@ -15,7 +15,11 @@ public class WorkspaceProjectFactoryServiceTests [Fact] public async Task CreateProjectAndBatch() { - using var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync(new LoggerFactory(), includeDevKitComponents: false); + using var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( + new LoggerFactory(), includeDevKitComponents: false, out var serverConfiguration); + + exportProvider.GetExportedValue() + .InitializeConfiguration(serverConfiguration); await exportProvider.GetExportedValue().CreateAsync(); var workspaceFactory = exportProvider.GetExportedValue(); From b63b5c57495d19cd2ce295ecd002fbcdff1a24fe Mon Sep 17 00:00:00 2001 From: Newrad0603 Date: Fri, 15 Mar 2024 12:29:14 -0400 Subject: [PATCH 083/170] Fix the incorrect usage of color tokens in the RenameFlyout and DisposableToolTip. The change makes those controls use DynamicResources for the color tokens rather than using the VSColorTheme helper or directly querying the Application-level resource dictionary. The change also removes an invalid set of ImageThemingUtilities.ImageBackgroundColor. (#72538) --- .../UI/Adornment/RenameFlyout.xaml | 45 ++++++++++--------- .../UI/Adornment/RenameFlyout.xaml.cs | 20 +-------- .../QuickInfo/ContentControlService.cs | 16 ++++--- 3 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml index 808ffca7525f9..cb01f2428b6ef 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml @@ -2,14 +2,14 @@ x:ClassModifier="internal" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:rename="clr-namespace:Microsoft.CodeAnalysis.Editor.Implementation.InlineRename" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" - xmlns:imageutils="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" xmlns:utils="clr-namespace:Microsoft.CodeAnalysis.Utilities" - mc:Ignorable="d" + xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0" + mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" MinWidth="200" KeyDown="Adornment_KeyDown" @@ -28,7 +28,9 @@ - @@ -44,18 +46,17 @@ HorizontalAlignment="Stretch"/> -