From 7ecfb9bad96325ac82010d152d69956856b6a60b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 5 Dec 2022 09:47:39 -0800 Subject: [PATCH 01/69] Simplify LoadableTextAndVersionSource Docs Simplify Rename Don't crate weak-ref if we're going to hold onto value strongly --- .../Solution/LoadableTextAndVersionSource.cs | 84 ++++++++++++++++++- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs index ba38c0915c6e8..84b5d290cb8be 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.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.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -16,12 +17,26 @@ internal sealed class LoadableTextAndVersionSource : ITextAndVersionSource private sealed class LazyValueWithOptions { public readonly LoadableTextAndVersionSource Source; - public readonly AsyncLazy LazyValue; public readonly LoadTextOptions Options; + private readonly SemaphoreSlim _gate = new(initialCount: 1); + + /// + /// Strong reference to the loaded text and version. Only held onto once computed if . is . Once held onto, this will be returned from all calls to + /// , or . + /// + private TextAndVersion? _instance; + + /// + /// Weak reference to the loaded text and version that we create whenever the value is computed. We will + /// attempt to return from this if still alive when clients call back into this. If neither this, nor are available, the value will be reloaded. + /// + private WeakReference? _weakInstance; + public LazyValueWithOptions(LoadableTextAndVersionSource source, LoadTextOptions options) { - LazyValue = new AsyncLazy(LoadAsync, LoadSynchronously, source.CacheResult); Source = source; Options = options; } @@ -31,6 +46,67 @@ private Task LoadAsync(CancellationToken cancellationToken) private TextAndVersion LoadSynchronously(CancellationToken cancellationToken) => Source.Loader.LoadTextSynchronously(Options, cancellationToken); + + public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value) + { + value = _instance; + if (value != null) + return true; + + return _weakInstance != null && _weakInstance.TryGetTarget(out value) && value != null; + } + + public TextAndVersion GetValue(CancellationToken cancellationToken) + { + if (!TryGetValue(out var textAndVersion)) + { + using (_gate.DisposableWait(cancellationToken)) + { + if (!TryGetValue(out textAndVersion)) + { + textAndVersion = LoadSynchronously(cancellationToken); + UpdateWeakAndStrongReferences_NoLock(textAndVersion); + } + } + } + + return textAndVersion; + } + + public async Task GetValueAsync(CancellationToken cancellationToken) + { + if (!TryGetValue(out var textAndVersion)) + { + using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + if (!TryGetValue(out textAndVersion)) + { + textAndVersion = await LoadAsync(cancellationToken).ConfigureAwait(false); + UpdateWeakAndStrongReferences_NoLock(textAndVersion); + } + } + } + + return textAndVersion; + } + + private void UpdateWeakAndStrongReferences_NoLock(TextAndVersion textAndVersion) + { + Contract.ThrowIfTrue(_gate.CurrentCount != 0); + + if (this.Source.CacheResult) + { + // if our source wants us to hold onto the value strongly, do so. No need to involve the weak-refs as + // this will now hold onto the value forever. + _instance = textAndVersion; + } + else + { + // Update the weak ref, so we can return the same instance if anything else is holding onto it. + _weakInstance ??= new WeakReference(textAndVersion); + _weakInstance.SetTarget(textAndVersion); + } + } } public readonly TextLoader Loader; @@ -47,7 +123,7 @@ public LoadableTextAndVersionSource(TextLoader loader, bool cacheResult) public bool CanReloadText => Loader.CanReloadText; - private AsyncLazy GetLazyValue(LoadTextOptions options) + private LazyValueWithOptions GetLazyValue(LoadTextOptions options) { var lazy = _lazyValue; @@ -57,7 +133,7 @@ private AsyncLazy GetLazyValue(LoadTextOptions options) _lazyValue = lazy = new LazyValueWithOptions(this, options); } - return lazy.LazyValue; + return lazy; } public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken) From b5531894a8e4a8b0f31b572130692909cb7b02e7 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 6 Mar 2023 13:27:25 -0600 Subject: [PATCH 02/69] Avoid allocations when adding all items in a HashSet to another Addresses 573MB out of 15169MB in a sample trace. --- ...SymbolUsageAnalysis.BasicBlockAnalysisData.cs | 6 ++++++ .../Core/Utilities/ICollectionExtensions.cs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs index 1b2ad0a5726e5..b547558641565 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs @@ -129,6 +129,9 @@ public bool Equals(BasicBlockAnalysisData other) { var writes1 = _reachingWrites[symbol]; var writes2 = other._reachingWrites[symbol]; + + // 📝 PERF: The boxed enumerator allocation that appears in some traces was fixed in .NET 8: + // https://github.com/dotnet/runtime/pull/78613 if (!writes1.SetEquals(writes2)) { return false; @@ -204,6 +207,9 @@ private static void AddEntries(Dictionary> re result.Add(symbol, values); } +#if NET + values.EnsureCapacity(values.Count + operations.Count); +#endif values.AddRange(operations); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs index 84acb5b771676..53e2040ecdc77 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ICollectionExtensions.cs @@ -26,6 +26,22 @@ public static void AddRange(this ICollection collection, IEnumerable? v } } + public static void AddRange(this ICollection collection, HashSet? values) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + if (values != null) + { + foreach (var item in values) + { + collection.Add(item); + } + } + } + public static void AddRange(this ICollection collection, ImmutableArray values) { if (collection == null) From bb53a879ada25c0e2be3b65390ba8506dcbb719e Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 6 Mar 2023 13:32:01 -0600 Subject: [PATCH 03/69] Avoid boxing SymbolDisplayPart in ToDisplayString Address 160MB of 15169MB in a sample trace. --- .../Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs index e2f51bc23036c..db575189d203f 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs @@ -41,7 +41,7 @@ public static string ToDisplayString(this ImmutableArray part var actualBuilder = pool.Builder; foreach (var part in parts) { - actualBuilder.Append(part); + actualBuilder.Append(part.ToString()); } return actualBuilder.ToString(); From 0f1f352b411ede2847145b9c168e74bf41459ce5 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 6 Mar 2023 13:34:39 -0600 Subject: [PATCH 04/69] Avoid boxing and enumerator allocations in FirstOrDefault Fixes 86MB of 15169MB in a sample trace. --- src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs index 29a8be5a5bdad..70a4c7370c399 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs @@ -5,7 +5,6 @@ #nullable disable using System.Diagnostics; -using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; @@ -26,7 +25,7 @@ internal static DeclarationModifiers MakeAndCheckNonTypeMemberModifiers( var result = modifiers.ToDeclarationModifiers(isForTypeDeclaration: false, diagnostics.DiagnosticBag ?? new DiagnosticBag(), isOrdinaryMethod: isOrdinaryMethod); result = CheckModifiers(isForTypeDeclaration: false, isForInterfaceMember, result, allowedModifiers, errorLocation, diagnostics, modifiers, out modifierErrors); - var readonlyToken = modifiers.FirstOrDefault(static t => t.Kind() == SyntaxKind.ReadOnlyKeyword); + var readonlyToken = modifiers.FirstOrDefault(SyntaxKind.ReadOnlyKeyword); if (readonlyToken.Parent is MethodDeclarationSyntax or AccessorDeclarationSyntax or BasePropertyDeclarationSyntax or EventDeclarationSyntax) modifierErrors |= !MessageID.IDS_FeatureReadOnlyMembers.CheckFeatureAvailability(diagnostics, readonlyToken.Parent, readonlyToken.GetLocation()); From 382c575cc65094d447d939b5f0c38167b553585c Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 6 Mar 2023 13:37:19 -0600 Subject: [PATCH 05/69] Avoid allocations related to GetCurrentWrites Fixes 100MB of 15169MB in a sample trace. --- .../SymbolUsageAnalysis.AnalysisData.cs | 12 ++-- ...bolUsageAnalysis.BasicBlockAnalysisData.cs | 19 +++++- ....DataFlowAnalyzer.FlowGraphAnalysisData.cs | 61 +++++++++++-------- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs index af6ff0d545911..c0ad85b57b3b1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs @@ -185,11 +185,13 @@ public void OnReadReferenceFound(ISymbol symbol) // Mark all the current reaching writes of symbol as read. if (SymbolsWriteBuilder.Count != 0) { - var currentWrites = CurrentBlockAnalysisData.GetCurrentWrites(symbol); - foreach (var write in currentWrites) - { - SymbolsWriteBuilder[(symbol, write)] = true; - } + CurrentBlockAnalysisData.ForEachCurrentWrite( + symbol, + static (write, arg) => + { + arg.self.SymbolsWriteBuilder[(arg.symbol, write)] = true; + }, + (symbol, self: this)); } // Mark the current symbol as read. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs index b547558641565..ae24533052337 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.BasicBlockAnalysisData.cs @@ -73,15 +73,30 @@ public void Clear(ISymbol symbol) /// /// Gets the currently reachable writes for the given symbol. /// - public IEnumerable GetCurrentWrites(ISymbol symbol) + public void ForEachCurrentWrite(ISymbol symbol, Action action, TArg arg) + { + ForEachCurrentWrite( + symbol, + static (write, arg) => + { + arg.action(write, arg.arg); + return true; + }, + (action, arg)); + } + + public bool ForEachCurrentWrite(ISymbol symbol, Func action, TArg arg) { if (_reachingWrites.TryGetValue(symbol, out var values)) { foreach (var value in values) { - yield return value; + if (!action(value, arg)) + return false; } } + + return true; } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs index 9acddc816678a..aae5710b03139 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs @@ -440,14 +440,13 @@ public void SetAnalysisDataOnMethodExit() { if (parameter.RefKind is RefKind.Ref or RefKind.Out) { - var currentWrites = CurrentBlockAnalysisData.GetCurrentWrites(parameter); - foreach (var write in currentWrites) - { - if (write != null) + CurrentBlockAnalysisData.ForEachCurrentWrite( + parameter, + static (write, arg) => { - SymbolsWriteBuilder[(parameter, write)] = true; - } - } + arg.self.SymbolsWriteBuilder[(arg.parameter, write)] = true; + }, + (parameter, self: this)); } } } @@ -519,28 +518,40 @@ public override void SetTargetsFromSymbolForDelegate(IOperation write, ISymbol s // Action y = x; // var targetsBuilder = PooledHashSet.GetInstance(); - foreach (var symbolWrite in CurrentBlockAnalysisData.GetCurrentWrites(symbol)) - { - if (symbolWrite == null) + var completedVisit = CurrentBlockAnalysisData.ForEachCurrentWrite( + symbol, + static (symbolWrite, arg) => { - continue; - } + if (symbolWrite == null) + { + // Continue with the iteration + return true; + } - if (!_reachingDelegateCreationTargets.TryGetValue(symbolWrite, out var targetsBuilderForSymbolWrite)) - { - // Unable to find delegate creation targets for this symbol write. - // Bail out without setting targets. - targetsBuilder.Free(); - return; - } - else - { - foreach (var target in targetsBuilderForSymbolWrite) + if (!arg.self._reachingDelegateCreationTargets.TryGetValue(symbolWrite, out var targetsBuilderForSymbolWrite)) { - targetsBuilder.Add(target); + // Unable to find delegate creation targets for this symbol write. + // Bail out without setting targets. + arg.targetsBuilder.Free(); + + // Stop iterating here, even if early + return false; } - } - } + else + { + foreach (var target in targetsBuilderForSymbolWrite) + { + arg.targetsBuilder.Add(target); + } + } + + // Continue with the iteration + return true; + }, + (targetsBuilder, self: this)); + + if (!completedVisit) + return; _reachingDelegateCreationTargets[write] = targetsBuilder; } From 8e63677e6ac909eeebda252e60185ca17e54ffe0 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 30 Mar 2023 16:58:56 -0500 Subject: [PATCH 06/69] Add a PerformanceSensitive constraint to disallow implicit boxing --- .../Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs index db575189d203f..9f125d8db51e7 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { @@ -18,6 +19,7 @@ public static class SymbolDisplayExtensions /// /// The array of parts. /// The concatenation of the parts into a single string. + [PerformanceSensitive("https://github.com/dotnet/roslyn/pull/67203", AllowImplicitBoxing = false)] public static string ToDisplayString(this ImmutableArray parts) { if (parts.IsDefault) From 68cf7031fe8a9170c46fcf0ba8237521e2dcf037 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 6 Apr 2023 00:35:12 -0700 Subject: [PATCH 07/69] Reduce number of invocations to StateSet.GetOrCreateActiveFileState Follow-up to #67662 Reduce the number of invocations to `StateSet.GetOrCreateActiveFileState`, which is called very frequently from all diagnostic computation paths and causes ConcurrentDictionary access. This change reduces the number of invocations to this method and subsequent `ActiveFileState.GetAnalysisData(kind)` by caching and passing around the computed state and existing data. --- .../DiagnosticIncrementalAnalyzer.Executor.cs | 15 ++-- ...lAnalyzer.IncrementalMemberEditAnalyzer.cs | 59 +++++++-------- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 24 +++---- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 72 ++++++++++--------- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 69 +++++++++--------- 5 files changed, 122 insertions(+), 117 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 28794cc60a74c..6614c91d79280 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -29,7 +29,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// for the given document/project. If suppressed, the caller does not need to compute the diagnostics for the given /// analyzer. Otherwise, diagnostics need to be computed. /// - private DocumentAnalysisData? TryGetCachedDocumentAnalysisData( + private (ActiveFileState, DocumentAnalysisData?) TryGetCachedDocumentAnalysisData( TextDocument document, StateSet stateSet, AnalysisKind kind, VersionStamp version, BackgroundAnalysisScope analysisScope, @@ -50,7 +50,7 @@ internal partial class DiagnosticIncrementalAnalyzer if (existingData.Version == version) { - return existingData; + return (state, existingData); } // Check whether analyzer is suppressed for project or document. @@ -60,7 +60,7 @@ internal partial class DiagnosticIncrementalAnalyzer isAnalyzerSuppressed = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || !IsAnalyzerEnabledForDocument(stateSet.Analyzer, existingData, analysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument); - return null; + return (state, null); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -130,7 +130,7 @@ static bool IsAnalyzerEnabledForDocument( /// Computes all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer). /// private static async Task ComputeDocumentAnalysisDataAsync( - DocumentAnalysisExecutor executor, StateSet stateSet, bool logTelemetry, CancellationToken cancellationToken) + DocumentAnalysisExecutor executor, DiagnosticAnalyzer analyzer, ActiveFileState state, bool logTelemetry, CancellationToken cancellationToken) { var kind = executor.AnalysisScope.Kind; var document = executor.AnalysisScope.TextDocument; @@ -139,18 +139,17 @@ private static async Task ComputeDocumentAnalysisDataAsync GetLogFunctionIdAndTitle(kind, out var functionId, out var title); var logLevel = logTelemetry ? LogLevel.Information : LogLevel.Trace; - using (Logger.LogBlock(functionId, GetDocumentLogMessage, title, document, stateSet.Analyzer, cancellationToken, logLevel: logLevel)) + using (Logger.LogBlock(functionId, GetDocumentLogMessage, title, document, analyzer, cancellationToken, logLevel: logLevel)) { try { - var diagnostics = await executor.ComputeDiagnosticsAsync(stateSet.Analyzer, cancellationToken).ConfigureAwait(false); + var diagnostics = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false); // this is no-op in product. only run in test environment Logger.Log(functionId, (t, d, a, ds) => $"{GetDocumentLogMessage(t, d, a)}, {string.Join(Environment.NewLine, ds)}", - title, document, stateSet.Analyzer, diagnostics); + title, document, analyzer, diagnostics); var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); - var state = stateSet.GetOrCreateActiveFileState(document.Id); var existingData = state.GetAnalysisData(kind); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs index 2f2aca5dc352d..658eb8fab40f9 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs @@ -45,7 +45,7 @@ public void UpdateDocumentWithCachedDiagnostics(Document document) public async Task>> ComputeDiagnosticsAsync( DocumentAnalysisExecutor executor, - ImmutableArray stateSets, + ImmutableArray analyzersWithState, VersionStamp version, Func>> computeAnalyzerDiagnosticsAsync, Func>>> computeDiagnosticsNonIncrementallyAsync, @@ -57,7 +57,7 @@ public async Task stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); + Debug.Assert(analyzersWithState.All(stateSet => stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); var document = (Document)analysisScope.TextDocument; var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -75,29 +75,30 @@ public async Task.GetInstance(out var spanBasedAnalyzers); - using var _2 = ArrayBuilder<(DiagnosticAnalyzer, DocumentAnalysisData)>.GetInstance(out var documentBasedAnalyzers); - (DiagnosticAnalyzer analyzer, DocumentAnalysisData existingData, bool spanBased)? compilerAnalyzerData = null; - foreach (var stateSet in stateSets) + using var _1 = ArrayBuilder.GetInstance(out var spanBasedAnalyzers); + using var _2 = ArrayBuilder.GetInstance(out var documentBasedAnalyzers); + (AnalyzerWithState analyzerWithState, bool spanBased)? compilerAnalyzerData = null; + foreach (var analyzerWithState in analyzersWithState) { // Check if we have existing cached diagnostics for this analyzer whose version matches the // old document version. If so, we can perform span based incremental analysis for the changed member. // Otherwise, we have to perform entire document analysis. - var state = stateSet.GetOrCreateActiveFileState(document.Id); - var existingData = state.GetAnalysisData(analysisScope.Kind); + var state = analyzerWithState.State; + var existingData = analyzerWithState.ExistingData; if (oldDocumentVersion == existingData.Version) { - if (!compilerAnalyzerData.HasValue && stateSet.Analyzer.IsCompilerAnalyzer()) - compilerAnalyzerData = (stateSet.Analyzer, existingData, spanBased: true); + if (!compilerAnalyzerData.HasValue && analyzerWithState.Analyzer.IsCompilerAnalyzer()) + compilerAnalyzerData = (analyzerWithState, spanBased: true); else - spanBasedAnalyzers.Add((stateSet.Analyzer, existingData)); + spanBasedAnalyzers.Add(analyzerWithState); } else { - if (!compilerAnalyzerData.HasValue && stateSet.Analyzer.IsCompilerAnalyzer()) - compilerAnalyzerData = (stateSet.Analyzer, DocumentAnalysisData.Empty, spanBased: false); + var analyzerWithStateAndEmptyData = new AnalyzerWithState(analyzerWithState.Analyzer, analyzerWithState.State, DocumentAnalysisData.Empty); + if (!compilerAnalyzerData.HasValue && analyzerWithState.Analyzer.IsCompilerAnalyzer()) + compilerAnalyzerData = (analyzerWithStateAndEmptyData, spanBased: false); else - documentBasedAnalyzers.Add((stateSet.Analyzer, DocumentAnalysisData.Empty)); + documentBasedAnalyzers.Add(analyzerWithStateAndEmptyData); } } @@ -126,47 +127,47 @@ public async Task oldMemberSpans, PooledDictionary> builder) { if (!compilerAnalyzerData.HasValue) return; - var (analyzer, existingData, spanBased) = compilerAnalyzerData.Value; + var (analyzerWithState, spanBased) = compilerAnalyzerData.Value; var span = spanBased ? changedMember.FullSpan : (TextSpan?)null; executor = executor.With(analysisScope.WithSpan(span)); - var analyzerAndExistingData = SpecializedCollections.SingletonEnumerable((analyzer, existingData)); - await ExecuteAnalyzersAsync(executor, analyzerAndExistingData, oldMemberSpans, builder).ConfigureAwait(false); + var analyzersWithState = SpecializedCollections.SingletonEnumerable(analyzerWithState); + await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteSpanBasedAnalyzersAsync( - ArrayBuilder<(DiagnosticAnalyzer, DocumentAnalysisData)> analyzersAndExistingData, + ArrayBuilder analyzersWithState, ImmutableArray oldMemberSpans, PooledDictionary> builder) { - if (analyzersAndExistingData.Count == 0) + if (analyzersWithState.Count == 0) return; executor = executor.With(analysisScope.WithSpan(changedMember.FullSpan)); - await ExecuteAnalyzersAsync(executor, analyzersAndExistingData, oldMemberSpans, builder).ConfigureAwait(false); + await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteDocumentBasedAnalyzersAsync( - ArrayBuilder<(DiagnosticAnalyzer, DocumentAnalysisData)> analyzersAndExistingData, + ArrayBuilder analyzersWithState, ImmutableArray oldMemberSpans, PooledDictionary> builder) { - if (analyzersAndExistingData.Count == 0) + if (analyzersWithState.Count == 0) return; executor = executor.With(analysisScope.WithSpan(null)); - await ExecuteAnalyzersAsync(executor, analyzersAndExistingData, oldMemberSpans, builder).ConfigureAwait(false); + await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); } async Task ExecuteAnalyzersAsync( DocumentAnalysisExecutor executor, - IEnumerable<(DiagnosticAnalyzer, DocumentAnalysisData)> analyzersAndExistingData, + IEnumerable analyzersWithState, ImmutableArray oldMemberSpans, PooledDictionary> builder) { @@ -175,9 +176,9 @@ async Task ExecuteAnalyzersAsync( Debug.Assert(changedMember != null); Debug.Assert(analysisScope.Kind == AnalysisKind.Semantic); - foreach (var (analyzer, existingData) in analyzersAndExistingData) + foreach (var analyzerWithState in analyzersWithState) { - var diagnostics = await computeAnalyzerDiagnosticsAsync(analyzer, executor, cancellationToken).ConfigureAwait(false); + var diagnostics = await computeAnalyzerDiagnosticsAsync(analyzerWithState.Analyzer, executor, cancellationToken).ConfigureAwait(false); // If we computed the diagnostics just for a span, then we are performing incremental analysis. // We need to compute the full document diagnostics by re-using diagnostics outside the changed @@ -187,12 +188,12 @@ async Task ExecuteAnalyzersAsync( Debug.Assert(analysisScope.Span.Value == changedMember.FullSpan); diagnostics = await GetUpdatedDiagnosticsForMemberEditAsync( - diagnostics, existingData, analyzer, + diagnostics, analyzerWithState.ExistingData, analyzerWithState.Analyzer, executor, changedMember, changedMemberId, oldMemberSpans, computeAnalyzerDiagnosticsAsync, cancellationToken).ConfigureAwait(false); } - builder.Add(analyzer, diagnostics); + builder.Add(analyzerWithState.Analyzer, diagnostics); } } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 19927eabf98fb..40953c6626787 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -162,12 +162,12 @@ private void ClearAllDiagnostics(ImmutableArray stateSets, ProjectId p } private void RaiseDiagnosticsCreated( - Project project, StateSet stateSet, ImmutableArray items, Action raiseEvents) + Project project, DiagnosticAnalyzer analyzer, ImmutableArray items, Action raiseEvents) { Contract.ThrowIfFalse(project.Solution.Workspace == Workspace); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(stateSet, project.Id, AnalysisKind.NonLocal), + CreateId(analyzer, project.Id, AnalysisKind.NonLocal), project.Solution.Workspace, project.Solution, project.Id, @@ -176,12 +176,12 @@ private void RaiseDiagnosticsCreated( } private void RaiseDiagnosticsRemoved( - ProjectId projectId, Solution? solution, StateSet stateSet, Action raiseEvents) + ProjectId projectId, Solution? solution, DiagnosticAnalyzer analyzer, Action raiseEvents) { Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(stateSet, projectId, AnalysisKind.NonLocal), + CreateId(analyzer, projectId, AnalysisKind.NonLocal), Workspace, solution, projectId, @@ -189,12 +189,12 @@ private void RaiseDiagnosticsRemoved( } private void RaiseDiagnosticsCreated( - TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) + TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, ImmutableArray items, Action raiseEvents) { Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsCreated( - CreateId(stateSet, document.Id, kind), + CreateId(analyzer, document.Id, kind), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, @@ -203,23 +203,23 @@ private void RaiseDiagnosticsCreated( } private void RaiseDiagnosticsRemoved( - DocumentId documentId, Solution? solution, StateSet stateSet, AnalysisKind kind, Action raiseEvents) + DocumentId documentId, Solution? solution, DiagnosticAnalyzer analyzer, AnalysisKind kind, Action raiseEvents) { Contract.ThrowIfFalse(solution == null || solution.Workspace == Workspace); raiseEvents(DiagnosticsUpdatedArgs.DiagnosticsRemoved( - CreateId(stateSet, documentId, kind), + CreateId(analyzer, documentId, kind), Workspace, solution, documentId.ProjectId, documentId)); } - private static object CreateId(StateSet stateSet, DocumentId documentId, AnalysisKind kind) - => new LiveDiagnosticUpdateArgsId(stateSet.Analyzer, documentId, kind); + private static object CreateId(DiagnosticAnalyzer analyzer, DocumentId documentId, AnalysisKind kind) + => new LiveDiagnosticUpdateArgsId(analyzer, documentId, kind); - private static object CreateId(StateSet stateSet, ProjectId projectId, AnalysisKind kind) - => new LiveDiagnosticUpdateArgsId(stateSet.Analyzer, projectId, kind); + private static object CreateId(DiagnosticAnalyzer analyzer, ProjectId projectId, AnalysisKind kind) + => new LiveDiagnosticUpdateArgsId(analyzer, projectId, kind); public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) => project.GetDependentVersionAsync(cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 90436c2447fca..5e8800ca77307 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -206,20 +206,20 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat var containsFullResult = true; // Try to get cached diagnostics, and also compute non-cached state sets that need diagnostic computation. - using var _1 = ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)>.GetInstance(out var syntaxAnalyzers); + using var _1 = ArrayBuilder.GetInstance(out var syntaxAnalyzers); // If we are performing incremental member edit analysis to compute diagnostics incrementally, // we divide the analyzers into those that support span-based incremental analysis and // those that do not support incremental analysis and must be executed for the entire document. // Otherwise, if we are not performing incremental analysis, all semantic analyzers are added // to the span-based analyzer set as we want to compute diagnostics only for the given span. - using var _2 = ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)>.GetInstance(out var semanticSpanBasedAnalyzers); - using var _3 = ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)>.GetInstance(out var semanticDocumentBasedAnalyzers); + using var _2 = ArrayBuilder.GetInstance(out var semanticSpanBasedAnalyzers); + using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); foreach (var stateSet in _stateSets) { var analyzer = stateSet.Analyzer; - if (!ShouldIncludeAnalyzer(analyzer, _shouldIncludeDiagnostic, _owner)) + if (!ShouldIncludeAnalyzer(analyzer, _shouldIncludeDiagnostic, _priorityProvider, _owner)) continue; bool includeSyntax = true, includeSemantic = true; @@ -234,22 +234,23 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat : _diagnosticKind == DiagnosticKind.AnalyzerSemantic; } + var lazyActiveFileState = new Lazy(() => stateSet.GetOrCreateActiveFileState(_document.Id)); if (includeSyntax) { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, list, cancellationToken).ConfigureAwait(false); + var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); if (!added) - syntaxAnalyzers.Add((stateSet, existingData)); + syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, lazyActiveFileState.Value, existingData!.Value)); } if (includeSemantic && _document is Document) { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Semantic, list, cancellationToken).ConfigureAwait(false); + var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Semantic, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); if (!added) { if (ShouldRunSemanticAnalysis(stateSet.Analyzer, _incrementalAnalysis, _blockForData, semanticSpanBasedAnalyzers, semanticDocumentBasedAnalyzers, out var stateSets)) { - stateSets.Add((stateSet, existingData)); + stateSets.Add(new AnalyzerWithState(stateSet.Analyzer, lazyActiveFileState.Value, existingData!.Value)); } else { @@ -278,15 +279,18 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat static bool ShouldIncludeAnalyzer( DiagnosticAnalyzer analyzer, Func? shouldIncludeDiagnostic, + ICodeActionRequestPriorityProvider priorityProvider, DiagnosticIncrementalAnalyzer owner) { + // Skip executing analyzer if its priority does not match the request priority. + if (!priorityProvider.MatchesPriority(analyzer)) + return false; + // Special case DocumentDiagnosticAnalyzer to never skip these document analyzers // based on 'shouldIncludeDiagnostic' predicate. More specifically, TS has special document // analyzer which report 0 supported diagnostics, but we always want to execute it. if (analyzer is DocumentDiagnosticAnalyzer) - { return true; - } // Skip analyzer if none of its reported diagnostics should be included. if (shouldIncludeDiagnostic != null && @@ -302,9 +306,9 @@ static bool ShouldRunSemanticAnalysis( DiagnosticAnalyzer analyzer, bool incrementalAnalysis, bool blockForData, - ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)> semanticSpanBasedAnalyzers, - ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)> semanticDocumentBasedAnalyzers, - [NotNullWhen(true)] out ArrayBuilder<(StateSet stateSet, DocumentAnalysisData? existingData)>? selectedStateSets) + ArrayBuilder semanticSpanBasedAnalyzers, + ArrayBuilder semanticDocumentBasedAnalyzers, + [NotNullWhen(true)] out ArrayBuilder? selectedStateSets) { // If the caller doesn't want us to force compute diagnostics, // we don't run semantic analysis. @@ -342,13 +346,15 @@ static bool ShouldRunSemanticAnalysis( private async Task<(bool added, DocumentAnalysisData? existingData)> TryAddCachedDocumentDiagnosticsAsync( StateSet stateSet, AnalysisKind kind, + Lazy lazyActiveFileState, ArrayBuilder list, CancellationToken cancellationToken) { - if (!stateSet.Analyzer.SupportAnalysisKind(kind) || - !_priorityProvider.MatchesPriority(stateSet.Analyzer)) + Debug.Assert(_priorityProvider.MatchesPriority(stateSet.Analyzer)); + + if (!stateSet.Analyzer.SupportAnalysisKind(kind)) { - // In the case where the analyzer doesn't support the requested kind or priority, act as if we succeeded, but just + // In the case where the analyzer doesn't support the requested kind, act as if we succeeded, but just // added no items to the result. Effectively we did add the cached values, just that all the values that could have // been added have been filtered out. We do not want to then compute the up to date values in the caller. return (true, null); @@ -356,7 +362,7 @@ static bool ShouldRunSemanticAnalysis( // make sure we get state even when none of our analyzer has ran yet. // but this shouldn't create analyzer that doesn't belong to this project (language) - var state = stateSet.GetOrCreateActiveFileState(_document.Id); + var state = lazyActiveFileState.Value; // see whether we can use existing info var existingData = state.GetAnalysisData(kind); @@ -376,7 +382,7 @@ static bool ShouldRunSemanticAnalysis( } private async Task ComputeDocumentDiagnosticsAsync( - ImmutableArray<(StateSet stateSet, DocumentAnalysisData? existingData)> stateSetsAndExistingData, + ImmutableArray analyzersWithState, AnalysisKind kind, TextSpan? span, ArrayBuilder builder, @@ -384,33 +390,32 @@ private async Task ComputeDocumentDiagnosticsAsync( CancellationToken cancellationToken) { Debug.Assert(!incrementalAnalysis || kind == AnalysisKind.Semantic); - Debug.Assert(!incrementalAnalysis || stateSetsAndExistingData.All(stateSetAndData => stateSetAndData.stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); + Debug.Assert(!incrementalAnalysis || analyzersWithState.All(analyzerWithState => analyzerWithState.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); - using var _ = ArrayBuilder.GetInstance(stateSetsAndExistingData.Length, out var stateSetBuilder); - foreach (var (stateSet, existingData) in stateSetsAndExistingData) + using var _ = ArrayBuilder.GetInstance(analyzersWithState.Length, out var filteredAnalyzersWithStateBuilder); + foreach (var analyzerWithState in analyzersWithState) { - var analyzer = stateSet.Analyzer; + var analyzer = analyzerWithState.Analyzer; if (_priorityProvider.MatchesPriority(analyzer)) { // Check if this is an expensive analyzer that needs to be de-prioritized to a lower priority bucket. // If so, we skip this analyzer from execution in the current priority bucket. // We will subsequently execute this analyzer in the lower priority bucket. - Contract.ThrowIfNull(existingData); - if (await TryDeprioritizeAnalyzerAsync(analyzer, existingData.Value).ConfigureAwait(false)) + if (await TryDeprioritizeAnalyzerAsync(analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) { continue; } - stateSetBuilder.Add(stateSet); + filteredAnalyzersWithStateBuilder.Add(analyzerWithState); } } - var stateSets = stateSetBuilder.ToImmutable(); + analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); - if (stateSets.IsEmpty) + if (analyzersWithState.IsEmpty) return; - var analyzers = stateSets.SelectAsArray(stateSet => stateSet.Analyzer); + var analyzers = analyzersWithState.SelectAsArray(stateSet => stateSet.Analyzer); var analysisScope = new DocumentAnalysisScope(_document, span, analyzers, kind); var executor = new DocumentAnalysisExecutor(analysisScope, _compilationWithAnalyzers, _owner._diagnosticAnalyzerRunner, _isExplicit, _logPerformanceInfo); var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); @@ -420,7 +425,7 @@ private async Task ComputeDocumentDiagnosticsAsync( { diagnosticsMap = await _owner._incrementalMemberEditAnalyzer.ComputeDiagnosticsAsync( executor, - stateSets, + analyzersWithState, version, ComputeDocumentDiagnosticsForAnalyzerCoreAsync, ComputeDocumentDiagnosticsCoreAsync, @@ -431,17 +436,16 @@ private async Task ComputeDocumentDiagnosticsAsync( diagnosticsMap = await ComputeDocumentDiagnosticsCoreAsync(executor, cancellationToken).ConfigureAwait(false); } - foreach (var stateSet in stateSets) + foreach (var analyzerWithState in analyzersWithState) { - var diagnostics = diagnosticsMap[stateSet.Analyzer]; + var diagnostics = diagnosticsMap[analyzerWithState.Analyzer]; builder.AddRange(diagnostics.Where(ShouldInclude)); // Save the computed diagnostics if caching is enabled and diagnostics were computed for the entire document. if (_cacheFullDocumentDiagnostics && !span.HasValue) { - var state = stateSet.GetOrCreateActiveFileState(_document.Id); var data = new DocumentAnalysisData(version, _text.Lines.Count, diagnostics); - state.Save(executor.AnalysisScope.Kind, data); + analyzerWithState.State.Save(executor.AnalysisScope.Kind, data); } } @@ -579,5 +583,7 @@ private bool ShouldInclude(DiagnosticData diagnostic) && (_shouldIncludeDiagnostic == null || _shouldIncludeDiagnostic(diagnostic.Id)); } } + + private record class AnalyzerWithState(DiagnosticAnalyzer Analyzer, ActiveFileState State, DocumentAnalysisData ExistingData); } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index fc96b9538c1a0..737af28892ad7 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -66,33 +66,33 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi // In near future, the diagnostic computation invocation into CompilationWithAnalyzers will be moved to OOP. // This should help simplify and/or remove the IDE layer diagnostic caching in devenv process. - // First attempt to fetch diagnostics from the cache, while computing the state sets for analyzers that are not cached. - using var _ = ArrayBuilder.GetInstance(out var nonCachedStateSets); + // First attempt to fetch diagnostics from the cache, while computing the analyzers that are not cached. + using var _ = ArrayBuilder<(DiagnosticAnalyzer analyzer, ActiveFileState state)>.GetInstance(out var nonCachedAnalyzersAndStates); foreach (var stateSet in stateSets) { - var data = TryGetCachedDocumentAnalysisData(document, stateSet, kind, version, + var (activeFileState, existingData) = TryGetCachedDocumentAnalysisData(document, stateSet, kind, version, backgroundAnalysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument, cancellationToken, out var isAnalyzerSuppressed); - if (data.HasValue) + if (existingData.HasValue) { - PersistAndRaiseDiagnosticsIfNeeded(data.Value, stateSet); + PersistAndRaiseDiagnosticsIfNeeded(existingData.Value, stateSet.Analyzer, activeFileState); } else if (!isAnalyzerSuppressed) { - nonCachedStateSets.Add(stateSet); + nonCachedAnalyzersAndStates.Add((stateSet.Analyzer, activeFileState)); } } // Then, compute the diagnostics for non-cached state sets, and cache and raise diagnostic reported events for these diagnostics. - if (nonCachedStateSets.Count > 0) + if (nonCachedAnalyzersAndStates.Count > 0) { - var analysisScope = new DocumentAnalysisScope(document, span: null, nonCachedStateSets.SelectAsArray(s => s.Analyzer), kind); + var analysisScope = new DocumentAnalysisScope(document, span: null, nonCachedAnalyzersAndStates.SelectAsArray(s => s.analyzer), kind); var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, isExplicit: false, logPerformanceInfo: false, onAnalysisException: OnAnalysisException); var logTelemetry = GlobalOptions.GetOption(DiagnosticOptionsStorage.LogTelemetryForBackgroundAnalyzerExecution); - foreach (var stateSet in nonCachedStateSets) + foreach (var (analyzer, state) in nonCachedAnalyzersAndStates) { - var computedData = await ComputeDocumentAnalysisDataAsync(executor, stateSet, logTelemetry, cancellationToken).ConfigureAwait(false); - PersistAndRaiseDiagnosticsIfNeeded(computedData, stateSet); + var computedData = await ComputeDocumentAnalysisDataAsync(executor, analyzer, state, logTelemetry, cancellationToken).ConfigureAwait(false); + PersistAndRaiseDiagnosticsIfNeeded(computedData, analyzer, state); } } } @@ -101,19 +101,18 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi throw ExceptionUtilities.Unreachable(); } - void PersistAndRaiseDiagnosticsIfNeeded(DocumentAnalysisData result, StateSet stateSet) + void PersistAndRaiseDiagnosticsIfNeeded(DocumentAnalysisData result, DiagnosticAnalyzer analyzer, ActiveFileState state) { if (result.FromCache == true) { - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items); + RaiseDocumentDiagnosticsIfNeeded(document, analyzer, kind, result.Items); return; } // no cancellation after this point. - var state = stateSet.GetOrCreateActiveFileState(document.Id); state.Save(kind, result.ToPersistData()); - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.OldItems, result.Items); + RaiseDocumentDiagnosticsIfNeeded(document, analyzer, kind, result.OldItems, result.Items); } void OnAnalysisException() @@ -325,9 +324,9 @@ private void RaiseDiagnosticsRemovedForDocument(DocumentId documentId, IEnumerab foreach (var stateSet in stateSets) { // clear all doucment diagnostics - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.Syntax, raiseEvents); - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.Semantic, raiseEvents); - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.NonLocal, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.Syntax, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.Semantic, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.NonLocal, raiseEvents); } }); } @@ -352,7 +351,7 @@ public Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellati foreach (var stateSet in stateSets) { // clear all project diagnostics - RaiseDiagnosticsRemoved(projectId, solution: null, stateSet, raiseEvents); + RaiseDiagnosticsRemoved(projectId, solution: null, stateSet.Analyzer, raiseEvents); } }); } @@ -508,17 +507,17 @@ private void RaiseProjectDiagnosticsIfNeeded( }); } - private void RaiseDocumentDiagnosticsIfNeeded(TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) - => RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray.Empty, items); + private void RaiseDocumentDiagnosticsIfNeeded(TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, ImmutableArray items) + => RaiseDocumentDiagnosticsIfNeeded(document, analyzer, kind, ImmutableArray.Empty, items); private void RaiseDocumentDiagnosticsIfNeeded( - TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) + TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) { - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, AnalyzerService.RaiseDiagnosticsUpdated, forceUpdate: false); + RaiseDocumentDiagnosticsIfNeeded(document, analyzer, kind, oldItems, newItems, AnalyzerService.RaiseDiagnosticsUpdated, forceUpdate: false); } private void RaiseDocumentDiagnosticsIfNeeded( - TextDocument document, StateSet stateSet, AnalysisKind kind, + TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, DiagnosticAnalysisResult oldResult, DiagnosticAnalysisResult newResult, Action raiseEvents) { @@ -535,11 +534,11 @@ private void RaiseDocumentDiagnosticsIfNeeded( var oldItems = oldResult.GetDocumentDiagnostics(document.Id, kind); var newItems = newResult.GetDocumentDiagnostics(document.Id, kind); - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, raiseEvents, forceUpdate); + RaiseDocumentDiagnosticsIfNeeded(document, analyzer, kind, oldItems, newItems, raiseEvents, forceUpdate); } private void RaiseDocumentDiagnosticsIfNeeded( - TextDocument document, StateSet stateSet, AnalysisKind kind, + TextDocument document, DiagnosticAnalyzer analyzer, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems, Action raiseEvents, bool forceUpdate) @@ -550,7 +549,7 @@ private void RaiseDocumentDiagnosticsIfNeeded( return; } - RaiseDiagnosticsCreated(document, stateSet, kind, newItems, raiseEvents); + RaiseDiagnosticsCreated(document, analyzer, kind, newItems, raiseEvents); } private async Task RaiseProjectDiagnosticsCreatedAsync(Project project, StateSet stateSet, DiagnosticAnalysisResult oldAnalysisResult, DiagnosticAnalysisResult newAnalysisResult, Action raiseEvents, CancellationToken cancellationToken) @@ -581,7 +580,7 @@ private async Task RaiseProjectDiagnosticsCreatedAsync(Project project, StateSet continue; } - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.NonLocal, oldAnalysisResult, newAnalysisResult, raiseEvents); + RaiseDocumentDiagnosticsIfNeeded(document, stateSet.Analyzer, AnalysisKind.NonLocal, oldAnalysisResult, newAnalysisResult, raiseEvents); // we don't raise events for active file. it will be taken cared by active file analysis if (stateSet.IsActiveFile(documentId)) @@ -589,18 +588,18 @@ private async Task RaiseProjectDiagnosticsCreatedAsync(Project project, StateSet continue; } - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.Syntax, oldAnalysisResult, newAnalysisResult, raiseEvents); - RaiseDocumentDiagnosticsIfNeeded(document, stateSet, AnalysisKind.Semantic, oldAnalysisResult, newAnalysisResult, raiseEvents); + RaiseDocumentDiagnosticsIfNeeded(document, stateSet.Analyzer, AnalysisKind.Syntax, oldAnalysisResult, newAnalysisResult, raiseEvents); + RaiseDocumentDiagnosticsIfNeeded(document, stateSet.Analyzer, AnalysisKind.Semantic, oldAnalysisResult, newAnalysisResult, raiseEvents); } - RaiseDiagnosticsCreated(project, stateSet, newAnalysisResult.GetOtherDiagnostics(), raiseEvents); + RaiseDiagnosticsCreated(project, stateSet.Analyzer, newAnalysisResult.GetOtherDiagnostics(), raiseEvents); } private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId projectId, IEnumerable documentIds, bool handleActiveFile, Action raiseEvents) { foreach (var documentId in documentIds) { - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.NonLocal, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.NonLocal, raiseEvents); // we don't raise events for active file. it will be taken care of by active file analysis if (!handleActiveFile && stateSet.IsActiveFile(documentId)) @@ -608,11 +607,11 @@ private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId project continue; } - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.Syntax, raiseEvents); - RaiseDiagnosticsRemoved(documentId, solution: null, stateSet, AnalysisKind.Semantic, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.Syntax, raiseEvents); + RaiseDiagnosticsRemoved(documentId, solution: null, stateSet.Analyzer, AnalysisKind.Semantic, raiseEvents); } - RaiseDiagnosticsRemoved(projectId, solution: null, stateSet, raiseEvents); + RaiseDiagnosticsRemoved(projectId, solution: null, stateSet.Analyzer, raiseEvents); } } } From 11480a3cb16aa90ccab2f4e3c66baeb2deece329 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 6 Apr 2023 01:19:10 -0700 Subject: [PATCH 08/69] Simplify --- ...gnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 5e8800ca77307..050f635c5d9fe 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -237,14 +237,14 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat var lazyActiveFileState = new Lazy(() => stateSet.GetOrCreateActiveFileState(_document.Id)); if (includeSyntax) { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Syntax, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); + var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Syntax, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); if (!added) syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, lazyActiveFileState.Value, existingData!.Value)); } if (includeSemantic && _document is Document) { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet, AnalysisKind.Semantic, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); + var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Semantic, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); if (!added) { if (ShouldRunSemanticAnalysis(stateSet.Analyzer, _incrementalAnalysis, _blockForData, @@ -344,15 +344,15 @@ static bool ShouldRunSemanticAnalysis( /// Note that this cached data may be from a prior document snapshot. /// private async Task<(bool added, DocumentAnalysisData? existingData)> TryAddCachedDocumentDiagnosticsAsync( - StateSet stateSet, + DiagnosticAnalyzer analyzer, AnalysisKind kind, Lazy lazyActiveFileState, ArrayBuilder list, CancellationToken cancellationToken) { - Debug.Assert(_priorityProvider.MatchesPriority(stateSet.Analyzer)); + Debug.Assert(_priorityProvider.MatchesPriority(analyzer)); - if (!stateSet.Analyzer.SupportAnalysisKind(kind)) + if (!analyzer.SupportAnalysisKind(kind)) { // In the case where the analyzer doesn't support the requested kind, act as if we succeeded, but just // added no items to the result. Effectively we did add the cached values, just that all the values that could have From e3bbd48063dc8ac53971b44f32fb2a7763fbab89 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 6 Apr 2023 01:24:21 -0700 Subject: [PATCH 09/69] Remove an additional redundant call to _priorityProvider.MatchesPriority(analyzer) --- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 050f635c5d9fe..48e3f69b078b6 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -395,19 +395,17 @@ private async Task ComputeDocumentDiagnosticsAsync( using var _ = ArrayBuilder.GetInstance(analyzersWithState.Length, out var filteredAnalyzersWithStateBuilder); foreach (var analyzerWithState in analyzersWithState) { - var analyzer = analyzerWithState.Analyzer; - if (_priorityProvider.MatchesPriority(analyzer)) - { - // Check if this is an expensive analyzer that needs to be de-prioritized to a lower priority bucket. - // If so, we skip this analyzer from execution in the current priority bucket. - // We will subsequently execute this analyzer in the lower priority bucket. - if (await TryDeprioritizeAnalyzerAsync(analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) - { - continue; - } + Debug.Assert(_priorityProvider.MatchesPriority(analyzerWithState.Analyzer)); - filteredAnalyzersWithStateBuilder.Add(analyzerWithState); + // Check if this is an expensive analyzer that needs to be de-prioritized to a lower priority bucket. + // If so, we skip this analyzer from execution in the current priority bucket. + // We will subsequently execute this analyzer in the lower priority bucket. + if (await TryDeprioritizeAnalyzerAsync(analyzerWithState.Analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) + { + continue; } + + filteredAnalyzersWithStateBuilder.Add(analyzerWithState); } analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); From 09182bfa845be2b3dc35fb85bc663137abb12f8e Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 6 Apr 2023 22:15:59 -0700 Subject: [PATCH 10/69] Address feedback --- ...lAnalyzer.IncrementalMemberEditAnalyzer.cs | 4 +- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 77 +++++++++---------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs index 658eb8fab40f9..8f1000a1794c8 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs @@ -137,7 +137,7 @@ async Task ExecuteCompilerAnalyzerAsync( var (analyzerWithState, spanBased) = compilerAnalyzerData.Value; var span = spanBased ? changedMember.FullSpan : (TextSpan?)null; executor = executor.With(analysisScope.WithSpan(span)); - var analyzersWithState = SpecializedCollections.SingletonEnumerable(analyzerWithState); + using var _ = ArrayBuilder.GetInstance(out var analyzersWithState); await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); } @@ -167,7 +167,7 @@ async Task ExecuteDocumentBasedAnalyzersAsync( async Task ExecuteAnalyzersAsync( DocumentAnalysisExecutor executor, - IEnumerable analyzersWithState, + ArrayBuilder analyzersWithState, ImmutableArray oldMemberSpans, PooledDictionary> builder) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 48e3f69b078b6..81ba6d4790f44 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -234,28 +234,35 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat : _diagnosticKind == DiagnosticKind.AnalyzerSemantic; } - var lazyActiveFileState = new Lazy(() => stateSet.GetOrCreateActiveFileState(_document.Id)); - if (includeSyntax) + if (includeSyntax || includeSemantic) { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Syntax, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); - if (!added) - syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, lazyActiveFileState.Value, existingData!.Value)); - } + var state = stateSet.GetOrCreateActiveFileState(_document.Id); - if (includeSemantic && _document is Document) - { - var (added, existingData) = await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Semantic, lazyActiveFileState, list, cancellationToken).ConfigureAwait(false); - if (!added) + if (includeSyntax && + analyzer.SupportAnalysisKind(AnalysisKind.Syntax)) { - if (ShouldRunSemanticAnalysis(stateSet.Analyzer, _incrementalAnalysis, _blockForData, - semanticSpanBasedAnalyzers, semanticDocumentBasedAnalyzers, out var stateSets)) - { - stateSets.Add(new AnalyzerWithState(stateSet.Analyzer, lazyActiveFileState.Value, existingData!.Value)); - } - else + var existingData = state.GetAnalysisData(AnalysisKind.Syntax); + if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Syntax, existingData, list, cancellationToken).ConfigureAwait(false)) + syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, state, existingData)); + } + + if (includeSemantic && + analyzer.SupportAnalysisKind(AnalysisKind.Semantic) && + _document is Document) + { + var existingData = state.GetAnalysisData(AnalysisKind.Semantic); + if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Semantic, existingData, list, cancellationToken).ConfigureAwait(false)) { - Debug.Assert(!_blockForData); - containsFullResult = false; + if (ShouldRunSemanticAnalysis(stateSet.Analyzer, _incrementalAnalysis, _blockForData, + semanticSpanBasedAnalyzers, semanticDocumentBasedAnalyzers, out var stateSets)) + { + stateSets.Add(new AnalyzerWithState(stateSet.Analyzer, state, existingData)); + } + else + { + Debug.Assert(!_blockForData); + containsFullResult = false; + } } } } @@ -338,34 +345,19 @@ static bool ShouldRunSemanticAnalysis( } /// - /// Returns a tuple with following fields: - /// 1. 'added': if we were able to add the cached diagnostics and we do not need to compute them fresh. - /// 2. 'existingData': Currently cached for the document being analyzed. - /// Note that this cached data may be from a prior document snapshot. + /// Returns if we were able to add the cached diagnostics and we do not need to compute them fresh. /// - private async Task<(bool added, DocumentAnalysisData? existingData)> TryAddCachedDocumentDiagnosticsAsync( + private async Task TryAddCachedDocumentDiagnosticsAsync( DiagnosticAnalyzer analyzer, AnalysisKind kind, - Lazy lazyActiveFileState, + DocumentAnalysisData existingData, ArrayBuilder list, CancellationToken cancellationToken) { + Debug.Assert(analyzer.SupportAnalysisKind(kind)); Debug.Assert(_priorityProvider.MatchesPriority(analyzer)); - if (!analyzer.SupportAnalysisKind(kind)) - { - // In the case where the analyzer doesn't support the requested kind, act as if we succeeded, but just - // added no items to the result. Effectively we did add the cached values, just that all the values that could have - // been added have been filtered out. We do not want to then compute the up to date values in the caller. - return (true, null); - } - - // make sure we get state even when none of our analyzer has ran yet. - // but this shouldn't create analyzer that doesn't belong to this project (language) - var state = lazyActiveFileState.Value; - // see whether we can use existing info - var existingData = state.GetAnalysisData(kind); var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); if (existingData.Version == version) { @@ -375,10 +367,10 @@ static bool ShouldRunSemanticAnalysis( list.Add(item); } - return (true, existingData); + return true; } - return (false, existingData); + return false; } private async Task ComputeDocumentDiagnosticsAsync( @@ -393,6 +385,7 @@ private async Task ComputeDocumentDiagnosticsAsync( Debug.Assert(!incrementalAnalysis || analyzersWithState.All(analyzerWithState => analyzerWithState.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); using var _ = ArrayBuilder.GetInstance(analyzersWithState.Length, out var filteredAnalyzersWithStateBuilder); + var anyDeprioritized = false; foreach (var analyzerWithState in analyzersWithState) { Debug.Assert(_priorityProvider.MatchesPriority(analyzerWithState.Analyzer)); @@ -402,13 +395,15 @@ private async Task ComputeDocumentDiagnosticsAsync( // We will subsequently execute this analyzer in the lower priority bucket. if (await TryDeprioritizeAnalyzerAsync(analyzerWithState.Analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) { + anyDeprioritized = true; continue; } filteredAnalyzersWithStateBuilder.Add(analyzerWithState); } - analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); + if (anyDeprioritized) + analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); if (analyzersWithState.IsEmpty) return; @@ -582,6 +577,6 @@ private bool ShouldInclude(DiagnosticData diagnostic) } } - private record class AnalyzerWithState(DiagnosticAnalyzer Analyzer, ActiveFileState State, DocumentAnalysisData ExistingData); + private sealed record class AnalyzerWithState(DiagnosticAnalyzer Analyzer, ActiveFileState State, DocumentAnalysisData ExistingData); } } From 2f2fdc9493049ab210ea6d5f5d8e9aa052824cd3 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 6 Apr 2023 22:18:32 -0700 Subject: [PATCH 11/69] Fix --- ...agnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs index 8f1000a1794c8..24fb3276f8cc3 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs @@ -137,7 +137,7 @@ async Task ExecuteCompilerAnalyzerAsync( var (analyzerWithState, spanBased) = compilerAnalyzerData.Value; var span = spanBased ? changedMember.FullSpan : (TextSpan?)null; executor = executor.With(analysisScope.WithSpan(span)); - using var _ = ArrayBuilder.GetInstance(out var analyzersWithState); + using var _ = ArrayBuilder.GetInstance(1, analyzerWithState, out var analyzersWithState); await ExecuteAnalyzersAsync(executor, analyzersWithState, oldMemberSpans, builder).ConfigureAwait(false); } From 14e95c7ebe635dffc46a6bd1d3d6a0064c25ab6a Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 10 Apr 2023 17:50:55 -0700 Subject: [PATCH 12/69] Correctly generate rename request --- .../Protocol/Extensions/Extensions.cs | 29 +++++- .../CodeActions/CodeActionResolveHandler.cs | 89 ++++++++++++++----- 2 files changed, 92 insertions(+), 26 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 42e5d7d8b6210..940fc3370fe4c 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -5,12 +5,11 @@ using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -27,7 +26,31 @@ public static Uri GetURI(this TextDocument document) Contract.ThrowIfNull(document.FilePath); return document is SourceGeneratedDocument ? ProtocolConversions.GetUriFromPartialFilePath(document.FilePath) - : ProtocolConversions.GetUriFromFilePath(document.FilePath); + : ProtocolConversions.GetUriFromFilePath(document.FilePath!); + } + + public static Uri GetUriFromName(this TextDocument document) + { + Contract.ThrowIfNull(document.FilePath); + Contract.ThrowIfNull(document.Name); + + var directoryName = Path.GetDirectoryName(document.FilePath); + Contract.ThrowIfNull(directoryName); + var path = Path.Combine(directoryName, document.Name); + return document is SourceGeneratedDocument + ? ProtocolConversions.GetUriFromPartialFilePath(path) + : ProtocolConversions.GetUriFromFilePath(path!); + } + + public static Uri GetUriFromContainingFolders(this TextDocument document) + { + Contract.ThrowIfTrue(document.Folders.Count == 0); + Contract.ThrowIfNull(document.Name); + + var path = Path.Combine(document.Folders.Concat(document.Name).AsArray()); + return document is SourceGeneratedDocument + ? ProtocolConversions.GetUriFromPartialFilePath(path) + : ProtocolConversions.GetUriFromFilePath(path!); } public static Uri? TryGetURI(this TextDocument document, RequestContext? context = null) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index c7b52ccb984f7..b21bbbc370837 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -264,15 +264,9 @@ async Task AddTextDocumentAdditionsAsync( var newTextDoc = getNewDocument(docId); Contract.ThrowIfNull(newTextDoc); - // Create the document as empty - textDocumentEdits.Add(new CreateFile { Uri = newTextDoc.GetURI() }); - - // And then give it content var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; - var edit = new TextEdit { Range = emptyDocumentRange, NewText = newText.ToString() }; - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); + var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetURI(), newText); + textDocumentEdits.AddRange(newTextDocumentEdits); } } @@ -289,29 +283,78 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); + // If the document has text change. + if (newTextDoc.HasTextChanged(oldTextDoc, ignoreUnchangeableDocument: false)) + { + var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + IEnumerable textChanges; - IEnumerable textChanges; + // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' + // method instead, we would get a change that spans the entire document, which we ideally want to avoid. + if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) + { + Contract.ThrowIfNull(textDiffService); + textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); + } + else + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + textChanges = newText.GetTextChanges(oldText); + } - // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' - // method instead, we would get a change that spans the entire document, which we ideally want to avoid. - if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) - { - Contract.ThrowIfNull(textDiffService); - textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); + var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); } - else + + // If the document has name/folder/filePath change + if (newTextDoc.HasInfoChanged(oldTextDoc)) { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText); - } + var oldDocumentAttribute = oldTextDoc.State.Attributes; + var newDocumentAttribute = newTextDoc.State.Attributes; + + // Rename + if (oldDocumentAttribute.Name != newDocumentAttribute.Name) + { + textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); + } - var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + // Move FilePath + if (oldDocumentAttribute.FilePath != newDocumentAttribute.FilePath) + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetURI(), newText); + textDocumentEdits.AddRange(newTextDocumentEdits); + textDocumentEdits.Add(new DeleteFile() { Uri = oldTextDoc.GetURI() }); + } + + // folder change + if (!oldDocumentAttribute.Folders.SequenceEqual(newDocumentAttribute.Folders)) + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetUriFromContainingFolders(), newText); + textDocumentEdits.AddRange(newTextDocumentEdits); + textDocumentEdits.Add(new DeleteFile() { Uri = oldTextDoc.GetURI() }); + } + } } } + + static SumType[] CopyDocumentToNewLocation(Uri newLocation, SourceText sourceText) + { + using var _ = ArrayBuilder>.GetInstance(capacity: 2, out var edits); + + // Create the document as empty + edits.Add(new CreateFile() { Uri = newLocation }); + + // And then give it content + var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; + var edit = new TextEdit { Range = emptyDocumentRange, NewText = sourceText.ToString() }; + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newLocation }; + edits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); + return edits.ToArray(); + } } } } From 911b79534e56f1fb7758914ea914b52be4785f06 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 10 Apr 2023 21:17:30 -0700 Subject: [PATCH 13/69] Add unit test --- .../CodeActions/CodeActionResolveTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index deaa7dcf9b2f1..927ad41c466ba 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Xunit; @@ -136,6 +137,52 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } + [WpfTheory, CombinatorialData] + public async Task Test(bool mutatingLspWorkspace) + { + var markUp = @" +class {|caret:ABC|} +{ +}"; + + await using var testLspServer = await CreateTestLspServerAsync(markUp, mutatingLspWorkspace); + var unresolvedCodeAction = CodeActionsTests.CreateCodeAction( + title: string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), + kind: CodeActionKind.Refactor, + children: Array.Empty(), + data: CreateCodeActionResolveData( + string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), + testLspServer.GetLocations("caret").Single()), + priority: VSInternalPriorityLevel.Normal, + groupName: "Roslyn2", + applicableRange: new LSP.Range { Start = new Position { Line = 0, Character = 6 }, End = new Position { Line = 0, Character = 9 } }, + diagnostics: null); + + var testWorkspace = testLspServer.TestWorkspace; + var documentBefore = testWorkspace.CurrentSolution.GetDocument(testWorkspace.Documents.Single().Id); + var documentUriBefore = documentBefore.GetUriFromName(); + + var actualResolvedAction = await RunGetCodeActionResolveAsync(testLspServer, unresolvedCodeAction); + + var documentAfter = testWorkspace.CurrentSolution.GetDocument(testWorkspace.Documents.Single().Id); + var documentUriAfter = documentBefore.WithName("ABC.cs").GetUriFromName(); + + var expectedCodeAction = CodeActionsTests.CreateCodeAction( + title: string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), + kind: CodeActionKind.Refactor, + children: Array.Empty(), + data: CreateCodeActionResolveData( + string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), + testLspServer.GetLocations("caret").Single()), + priority: VSInternalPriorityLevel.Normal, + groupName: "Roslyn2", + applicableRange: new LSP.Range { Start = new Position { Line = 0, Character = 6 }, End = new Position { Line = 0, Character = 9 } }, + diagnostics: null, + edit: GenerateRenameFileEdit(new List<(Uri, Uri)> { (documentUriBefore, documentUriAfter) })); + + AssertJsonEquals(expectedCodeAction, actualResolvedAction); + } + private static async Task RunGetCodeActionResolveAsync( TestLspServer testLspServer, VSInternalCodeAction unresolvedCodeAction) @@ -169,5 +216,12 @@ private static WorkspaceEdit GenerateWorkspaceEdit( } } }; + + private static WorkspaceEdit GenerateRenameFileEdit(IList<(Uri oldUri, Uri newUri)> renameLocations) + => new() + { + DocumentChanges = renameLocations.Select( + locations => new SumType(new RenameFile() { OldUri = locations.oldUri, NewUri = locations.newUri })).ToArray() + }; } } From 63b47cd74dbed86bf85a604ebb9e674f5f78293e Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 10 Apr 2023 21:25:23 -0700 Subject: [PATCH 14/69] Simplify code --- .../Protocol/Extensions/Extensions.cs | 6 ++-- .../CodeActions/CodeActionResolveHandler.cs | 31 +++++++++++-------- .../CodeActions/CodeActionResolveTests.cs | 1 - 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 940fc3370fe4c..d6216e55e3571 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -26,7 +26,7 @@ public static Uri GetURI(this TextDocument document) Contract.ThrowIfNull(document.FilePath); return document is SourceGeneratedDocument ? ProtocolConversions.GetUriFromPartialFilePath(document.FilePath) - : ProtocolConversions.GetUriFromFilePath(document.FilePath!); + : ProtocolConversions.GetUriFromFilePath(document.FilePath); } public static Uri GetUriFromName(this TextDocument document) @@ -39,7 +39,7 @@ public static Uri GetUriFromName(this TextDocument document) var path = Path.Combine(directoryName, document.Name); return document is SourceGeneratedDocument ? ProtocolConversions.GetUriFromPartialFilePath(path) - : ProtocolConversions.GetUriFromFilePath(path!); + : ProtocolConversions.GetUriFromFilePath(path); } public static Uri GetUriFromContainingFolders(this TextDocument document) @@ -50,7 +50,7 @@ public static Uri GetUriFromContainingFolders(this TextDocument document) var path = Path.Combine(document.Folders.Concat(document.Name).AsArray()); return document is SourceGeneratedDocument ? ProtocolConversions.GetUriFromPartialFilePath(path) - : ProtocolConversions.GetUriFromFilePath(path!); + : ProtocolConversions.GetUriFromFilePath(path); } public static Uri? TryGetURI(this TextDocument document, RequestContext? context = null) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index b21bbbc370837..04f6b05feae98 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -265,8 +265,7 @@ async Task AddTextDocumentAdditionsAsync( Contract.ThrowIfNull(newTextDoc); var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetURI(), newText); - textDocumentEdits.AddRange(newTextDocumentEdits); + CopyDocumentToNewLocation(newTextDoc.GetURI(), newText, textDocumentEdits); } } @@ -324,36 +323,42 @@ async Task AddTextDocumentEditsAsync( if (oldDocumentAttribute.FilePath != newDocumentAttribute.FilePath) { var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetURI(), newText); - textDocumentEdits.AddRange(newTextDocumentEdits); - textDocumentEdits.Add(new DeleteFile() { Uri = oldTextDoc.GetURI() }); + CutDocumentToNewLocation(newTextDoc.GetURI(), oldTextDoc.GetURI(), newText, textDocumentEdits); } // folder change if (!oldDocumentAttribute.Folders.SequenceEqual(newDocumentAttribute.Folders)) { var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - var newTextDocumentEdits = CopyDocumentToNewLocation(newTextDoc.GetUriFromContainingFolders(), newText); - textDocumentEdits.AddRange(newTextDocumentEdits); - textDocumentEdits.Add(new DeleteFile() { Uri = oldTextDoc.GetURI() }); + CutDocumentToNewLocation(newTextDoc.GetUriFromContainingFolders(), oldTextDoc.GetUriFromContainingFolders(), newText, textDocumentEdits); } } } } - static SumType[] CopyDocumentToNewLocation(Uri newLocation, SourceText sourceText) + static void CutDocumentToNewLocation( + Uri newLocation, + Uri oldLocation, + SourceText sourceText, + ArrayBuilder> textDocumentEdits) { - using var _ = ArrayBuilder>.GetInstance(capacity: 2, out var edits); + CopyDocumentToNewLocation(newLocation, sourceText, textDocumentEdits); + textDocumentEdits.Add(new DeleteFile() { Uri = oldLocation }); + } + static void CopyDocumentToNewLocation( + Uri newLocation, + SourceText sourceText, + ArrayBuilder> textDocumentEdits) + { // Create the document as empty - edits.Add(new CreateFile() { Uri = newLocation }); + textDocumentEdits.Add(new CreateFile() { Uri = newLocation }); // And then give it content var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; var edit = new TextEdit { Range = emptyDocumentRange, NewText = sourceText.ToString() }; var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newLocation }; - edits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); - return edits.ToArray(); + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index 927ad41c466ba..f51e3ddd0ed88 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Xunit; From eded74b25c1db9952a0831e4298e260dd7747c60 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 11 Apr 2023 07:04:48 -0700 Subject: [PATCH 15/69] Support attributes on primary constructors (#67695) https://github.com/dotnet/csharplang/blob/main/proposals/primary-constructors.md#attributes-targeting-primary-constructors --- .../BinderFactory.BinderFactoryVisitor.cs | 2 +- .../CSharp/Portable/Binder/Binder_Lookup.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 2 +- .../Declarations/DeclarationTreeBuilder.cs | 3 +- .../Source/SourceConstructorSymbolBase.cs | 10 +- .../Source/SourceMemberContainerSymbol.cs | 15 +- .../SourceMethodSymbolWithAttributes.cs | 11 +- .../Symbols/Source/SourceNamedTypeSymbol.cs | 2 +- .../Portable/Symbols/Symbol_Attributes.cs | 8 +- .../Records/SynthesizedPrimaryConstructor.cs | 69 ++++ .../Semantics/PrimaryConstructorTests.cs | 364 ++++++++++++++++++ 11 files changed, 466 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 88e343755753b..13e97132b18c4 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -544,7 +544,7 @@ private Symbol GetMemberSymbol(string memberName, TextSpan memberSpan, NamedType { Debug.Assert(kind is SymbolKind.Method or SymbolKind.Property or SymbolKind.Event); - if (container is SourceMemberContainerTypeSymbol { PrimaryConstructor: not null } sourceMemberContainerTypeSymbol) + if (container is SourceMemberContainerTypeSymbol { HasPrimaryConstructor: true } sourceMemberContainerTypeSymbol) { foreach (Symbol sym in sourceMemberContainerTypeSymbol.GetMembersToMatchAgainstDeclarationSpan()) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index 6463199811307..d3cbe2574fbca 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -1304,7 +1304,7 @@ internal static ImmutableArray GetCandidateMembers(NamespaceOrTypeSymbol { return ImmutableArray.Empty; } - else if (nsOrType is SourceMemberContainerTypeSymbol { PrimaryConstructor: not null } sourceMemberContainerTypeSymbol) + else if (nsOrType is SourceMemberContainerTypeSymbol { HasPrimaryConstructor: true } sourceMemberContainerTypeSymbol) { return sourceMemberContainerTypeSymbol.GetCandidateMembersForLookup(name); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 062ed513d3450..cc10d881f3e13 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3652,7 +3652,7 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, constructor.Body == null ? diagnostics : BindingDiagnosticBag.Discarded)); bool hasPrimaryConstructor() => - ContainingType is SourceMemberContainerTypeSymbol { PrimaryConstructor: not null }; + ContainingType is SourceMemberContainerTypeSymbol { HasPrimaryConstructor: true }; bool isInstanceConstructor(out MethodSymbol constructorSymbol) { diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs index 083f88688a0f8..96b75c66a830d 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs @@ -613,8 +613,7 @@ private SingleNamespaceOrTypeDeclaration VisitTypeDeclaration(TypeDeclarationSyn Symbol.ReportErrorIfHasConstraints(node.ConstraintClauses, diagnostics); } - // A type with parameters at least has a primary constructor - var hasPrimaryCtor = node.ParameterList != null; + var hasPrimaryCtor = node.ParameterList != null && node is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax; if (hasPrimaryCtor) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index 520b9aa43f75c..ff7adaaeafb06 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -175,14 +175,6 @@ internal sealed override OneOrMany> GetReturnTyp return OneOrMany.Create(default(SyntaxList)); } - protected sealed override IAttributeTargetSymbol AttributeOwner - { - get - { - return base.AttributeOwner; - } - } - internal sealed override bool GenerateDebugInfo { get { return true; } @@ -252,7 +244,7 @@ internal sealed override int CalculateLocalSyntaxOffset(int position, SyntaxTree protected sealed override bool HasSetsRequiredMembersImpl => GetEarlyDecodedWellKnownAttributeData()?.HasSetsRequiredMembersAttribute == true; - internal sealed override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) + internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) { if (arguments.SymbolPart == AttributeLocation.None) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index cdb108c1d88b5..75a8d2b16f2c2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -252,6 +252,7 @@ internal SourceMemberContainerTypeSymbol( : SpecialType.None; _flags = new Flags(specialType, typeKind, declaration.HasPrimaryConstructor); + Debug.Assert(typeKind is TypeKind.Struct or TypeKind.Class || !HasPrimaryConstructor); var containingType = this.ContainingType; if (containingType?.IsSealed == true && this.DeclaredAccessibility.HasProtected()) @@ -3200,20 +3201,28 @@ ImmutableArray buildSimpleProgramEntry } } + internal bool HasPrimaryConstructor => this._flags.HasPrimaryConstructor; + internal SynthesizedPrimaryConstructor? PrimaryConstructor { get { - if (!this._flags.HasPrimaryConstructor) + if (!HasPrimaryConstructor) return null; var declared = Volatile.Read(ref _lazyDeclaredMembersAndInitializers); + SynthesizedPrimaryConstructor? result; if (declared is not null && declared != DeclaredMembersAndInitializers.UninitializedSentinel) { - return declared.PrimaryConstructor; + result = declared.PrimaryConstructor; + } + else + { + result = GetMembersAndInitializers().PrimaryConstructor; } - return GetMembersAndInitializers().PrimaryConstructor; + Debug.Assert(result is object); + return result; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 893d1d7a431d6..79f805093c5a8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -128,6 +128,11 @@ protected virtual IAttributeTargetSymbol AttributeOwner get { return this; } } + protected virtual AttributeLocation AttributeLocationForLoadAndValidateAttributes + { + get { return AttributeLocation.None; } + } + IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner { get { return this.AttributeOwner; } @@ -280,7 +285,7 @@ private CustomAttributesBag GetAttributesBag(ref CustomAttr { var (declarations, symbolPart) = forReturnType ? (GetReturnTypeAttributeDeclarations(), AttributeLocation.Return) - : (GetAttributeDeclarations(), AttributeLocation.None); + : (GetAttributeDeclarations(), AttributeLocationForLoadAndValidateAttributes); bagCreatedOnThisThread = LoadAndValidateAttributes( declarations, ref lazyCustomAttributesBag, @@ -476,7 +481,7 @@ internal sealed override ImmutableArray GetAppliedConditionalSymbols() return data != null ? data.ConditionalSymbols : ImmutableArray.Empty; } - protected sealed override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments arguments) + protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments arguments) { Debug.Assert(!arguments.Attribute.HasErrors); Debug.Assert(arguments.SymbolPart == AttributeLocation.None || arguments.SymbolPart == AttributeLocation.Return); @@ -1015,7 +1020,7 @@ static UnmanagedCallersOnlyAttributeData DecodeUnmanagedCallersOnlyAttributeData } } - internal sealed override void PostDecodeWellKnownAttributes(ImmutableArray boundAttributes, ImmutableArray allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData) + internal override void PostDecodeWellKnownAttributes(ImmutableArray boundAttributes, ImmutableArray allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData) { Debug.Assert(!boundAttributes.IsDefault); Debug.Assert(!allAttributeSyntaxNodes.IsDefault); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index ce3cf12ddc9e7..c861144f1cd16 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -821,7 +821,7 @@ AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations case TypeKind.Struct: case TypeKind.Class: - return AttributeLocation.Type; + return AttributeLocation.Type | (HasPrimaryConstructor ? AttributeLocation.Method : 0); default: return AttributeLocation.None; diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs index 9b50cfc1d6b7f..f8f464d31506e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs @@ -590,7 +590,8 @@ private ImmutableArray GetAttributesToBind( foreach (var attributeDeclarationSyntax in attributeDeclarationSyntaxList) { // We bind the attribute only if it has a matching target for the given ownerSymbol and attributeLocation. - if (MatchAttributeTarget(attributeTarget, symbolPart, attributeDeclarationSyntax.Target, diagnostics)) + if (MatchAttributeTarget(attributeTarget, symbolPart, attributeDeclarationSyntax.Target, diagnostics) && + ShouldBindAttributes(attributeDeclarationSyntax, diagnostics)) { if (syntaxBuilder == null) { @@ -644,6 +645,11 @@ private ImmutableArray GetAttributesToBind( } } + protected virtual bool ShouldBindAttributes(AttributeListSyntax attributeDeclarationSyntax, BindingDiagnosticBag diagnostics) + { + return true; + } + #nullable enable private Binder GetAttributeBinder(SyntaxList attributeDeclarationSyntaxList, CSharpCompilation compilation, Binder? rootBinder = null) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPrimaryConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPrimaryConstructor.cs index f737464c83c8c..2703847c97b27 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPrimaryConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPrimaryConstructor.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; @@ -21,6 +22,9 @@ public SynthesizedPrimaryConstructor( base(containingType, syntax.Identifier.GetLocation(), syntax, isIterator: false) { Debug.Assert(syntax.Kind() is SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration); + Debug.Assert(containingType.HasPrimaryConstructor); + Debug.Assert(containingType is SourceNamedTypeSymbol); + Debug.Assert(containingType is IAttributeTargetSymbol); this.MakeFlags( MethodKind.Constructor, @@ -36,6 +40,21 @@ internal TypeDeclarationSyntax GetSyntax() return (TypeDeclarationSyntax)syntaxReferenceOpt.GetSyntax(); } + protected override IAttributeTargetSymbol AttributeOwner + { + get { return (IAttributeTargetSymbol)ContainingType; } + } + + protected override AttributeLocation AttributeLocationForLoadAndValidateAttributes + { + get { return AttributeLocation.Method; } + } + + internal override OneOrMany> GetAttributeDeclarations() + { + return new OneOrMany>(((SourceNamedTypeSymbol)ContainingType).GetAttributeDeclarations()); + } + protected override ParameterListSyntax GetParameterList() { return GetSyntax().ParameterList!; @@ -99,5 +118,55 @@ public IReadOnlyDictionary GetCapturedParameters() Interlocked.CompareExchange(ref _capturedParameters, Binder.CapturedParametersFinder.GetCapturedParameters(this), null); return _capturedParameters; } + + internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) + { + Debug.Assert(arguments.SymbolPart == AttributeLocation.Method); + arguments.SymbolPart = AttributeLocation.None; + var result = base.EarlyDecodeWellKnownAttribute(ref arguments); + arguments.SymbolPart = AttributeLocation.Method; + return result; + } + + protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments arguments) + { + Debug.Assert(arguments.SymbolPart == AttributeLocation.Method); + arguments.SymbolPart = AttributeLocation.None; + base.DecodeWellKnownAttributeImpl(ref arguments); + arguments.SymbolPart = AttributeLocation.Method; + } + + internal override void PostDecodeWellKnownAttributes(ImmutableArray boundAttributes, ImmutableArray allAttributeSyntaxNodes, BindingDiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData) + { + Debug.Assert(symbolPart is AttributeLocation.Method or AttributeLocation.Return); + base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart is AttributeLocation.Method ? AttributeLocation.None : symbolPart, decodedData); + } + + protected override bool ShouldBindAttributes(AttributeListSyntax attributeDeclarationSyntax, BindingDiagnosticBag diagnostics) + { + Debug.Assert(attributeDeclarationSyntax.Target is object); + + if (!base.ShouldBindAttributes(attributeDeclarationSyntax, diagnostics)) + { + return false; + } + + if (attributeDeclarationSyntax.SyntaxTree == SyntaxRef.SyntaxTree && + GetSyntax().AttributeLists.Contains(attributeDeclarationSyntax)) + { + if (ContainingType is { IsRecord: true } or { IsRecordStruct: true }) + { + MessageID.IDS_FeaturePrimaryConstructors.CheckFeatureAvailability(diagnostics, attributeDeclarationSyntax, attributeDeclarationSyntax.Target.Identifier.GetLocation()); + } + + return true; + } + + SyntaxToken target = attributeDeclarationSyntax.Target.Identifier; + diagnostics.Add(ErrorCode.WRN_AttributeLocationOnBadDeclaration, + target.GetLocation(), target.ToString(), (AttributeOwner.AllowedAttributeLocations & ~AttributeLocation.Method).ToDisplayString()); + + return false; + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs index 28654bc28f2b7..835ac4a7eb55e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs @@ -3064,6 +3064,370 @@ public static void Main() comp.VerifyDiagnostics(); } + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_01([CombinatorialValues("class", "struct", "record", "record class", "record struct")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[method: A] +" + declaration + @" C + (); +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + verify(comp); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + verify(comp); + + comp = CreateCompilation(source, parseOptions: TestOptions.Regular11); + + if (declaration is "class" or "struct") + { + comp.VerifyDiagnostics( + // (9,5): error CS8652: The feature 'primary constructors' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "()").WithArguments("primary constructors").WithLocation(9, 5) + ); + } + else + { + comp.VerifyDiagnostics( + // (7,2): error CS8652: The feature 'primary constructors' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [method: A] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "method").WithArguments("primary constructors").WithLocation(7, 2) + ); + } + + verify(comp); + + static void verify(CSharpCompilation comp) + { + var c = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C"); + Assert.Empty(c.GetAttributes()); + Assert.True(c.HasPrimaryConstructor); + Assert.Equal("A", c.PrimaryConstructor.GetAttributes().Single().ToString()); + Assert.True(c.Constructors.Where(ctor => ctor != c.PrimaryConstructor).All(ctor => ctor.GetAttributes().IsEmpty)); + } + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_02([CombinatorialValues("class C();", "struct C();", "record C();", "record class C();", "record struct C();")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[return: A] +" + declaration + @" +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type, method'. All attributes in this block will be ignored. + // [return: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type, method").WithLocation(7, 2) + ); + + var c = comp.GetTypeByMetadataName("C"); + Assert.Empty(c.GetAttributes()); + Assert.True(c.Constructors.All(ctor => ctor.GetAttributes().IsEmpty)); + Assert.True(c.Constructors.All(ctor => ctor.GetReturnTypeAttributes().IsEmpty)); + } + + [Fact] + public void AttributesOnPrimaryConstructor_03() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[method: A] +[return: A] +interface I(); +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(7, 2), + // (8,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [return: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type").WithLocation(8, 2), + // (9,12): error CS9122: Unexpected parameter list. + // interface I(); + Diagnostic(ErrorCode.ERR_UnexpectedParameterList, "()").WithLocation(9, 12) + ); + + var i = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("I"); + Assert.Empty(i.GetAttributes()); + Assert.False(i.HasPrimaryConstructor); + Assert.Null(i.PrimaryConstructor); + Assert.Empty(i.Constructors); + } + + [Fact] + public void AttributesOnPrimaryConstructor_04() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[method: A] +[return: A] +enum E(); +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(7, 2), + // (8,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [return: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type").WithLocation(8, 2), + // (9,7): error CS1514: { expected + // enum E(); + Diagnostic(ErrorCode.ERR_LbraceExpected, "(").WithLocation(9, 7), + // (9,7): error CS1513: } expected + // enum E(); + Diagnostic(ErrorCode.ERR_RbraceExpected, "(").WithLocation(9, 7), + // (9,7): error CS8803: Top-level statements must precede namespace and type declarations. + // enum E(); + Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "();").WithLocation(9, 7), + // (9,8): error CS1525: Invalid expression term ')' + // enum E(); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(9, 8) + ); + + var e = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("E"); + Assert.Empty(e.GetAttributes()); + Assert.False(e.HasPrimaryConstructor); + Assert.Null(e.PrimaryConstructor); + Assert.True(e.Constructors.All(ctor => ctor.GetAttributes().IsEmpty)); + Assert.True(e.Constructors.All(ctor => ctor.GetReturnTypeAttributes().IsEmpty)); + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_05([CombinatorialValues("class C;", "struct C;", "record C;", "record class C;", "record struct C;", "interface C;", "enum C;")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[method: A] +[return: A] +" + declaration + @" +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(7, 2), + // (8,2): warning CS0657: 'return' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [return: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "return").WithArguments("return", "type").WithLocation(8, 2) + ); + + var c = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C"); + Assert.Empty(c.GetAttributes()); + Assert.False(c.HasPrimaryConstructor); + Assert.Null(c.PrimaryConstructor); + Assert.True(c.Constructors.All(ctor => ctor.GetAttributes().IsEmpty)); + Assert.True(c.Constructors.All(ctor => ctor.GetReturnTypeAttributes().IsEmpty)); + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_06([CombinatorialValues("class C();", "struct C();", "record C();", "record class C();", "record struct C();")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[type: A] +" + declaration + @" +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var c = comp.GetTypeByMetadataName("C"); + Assert.Equal("A", c.GetAttributes().Single().ToString()); + Assert.True(c.Constructors.All(ctor => ctor.GetAttributes().IsEmpty)); + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_07([CombinatorialValues("class C();", "struct C();", "record C();", "record class C();", "record struct C();")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[A] +" + declaration + @" +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var c = comp.GetTypeByMetadataName("C"); + Assert.Equal("A", c.GetAttributes().Single().ToString()); + Assert.True(c.Constructors.All(ctor => ctor.GetAttributes().IsEmpty)); + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_08([CombinatorialValues("class", "struct", "record", "record class", "record struct")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[method: A] +partial " + declaration + @" C1(); + +[method: B] +partial " + declaration + @" C1; + +[method: B] +partial " + declaration + @" C2; + +[method: A] +partial " + declaration + @" C2(); +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (15,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: B] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(15, 2), + // (18,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: B] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(18, 2) + ); + + var c1 = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C1"); + Assert.Empty(c1.GetAttributes()); + Assert.True(c1.HasPrimaryConstructor); + Assert.Equal("A", c1.PrimaryConstructor.GetAttributes().Single().ToString()); + Assert.True(c1.Constructors.Where(ctor => ctor != c1.PrimaryConstructor).All(ctor => ctor.GetAttributes().IsEmpty)); + + var c2 = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C2"); + Assert.Empty(c2.GetAttributes()); + Assert.True(c2.HasPrimaryConstructor); + Assert.Equal("A", c2.PrimaryConstructor.GetAttributes().Single().ToString()); + Assert.True(c2.Constructors.Where(ctor => ctor != c2.PrimaryConstructor).All(ctor => ctor.GetAttributes().IsEmpty)); + } + + [Theory] + [CombinatorialData] + public void AttributesOnPrimaryConstructor_09([CombinatorialValues("class", "struct", "record", "record class", "record struct")] string declaration) + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[method: A] +partial " + declaration + @" C1(); + +#line 100 +[method: B] +partial " + declaration + @" C1 +#line 200 + (); + +[method: B] +partial " + declaration + @" C2(); + +#line 300 +[method: A] +partial " + declaration + @" C2 +#line 400 + (); +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (100,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: B] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(100, 2), + // (200,5): error CS8863: Only a single partial type declaration may have a parameter list + // (); + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(200, 5), + // (300,2): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. + // [method: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "type").WithLocation(300, 2), + // (400,5): error CS8863: Only a single partial type declaration may have a parameter list + // (); + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(400, 5) + ); + + var c1 = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C1"); + Assert.Empty(c1.GetAttributes()); + Assert.True(c1.HasPrimaryConstructor); + Assert.Equal("A", c1.PrimaryConstructor.GetAttributes().Single().ToString()); + Assert.True(c1.Constructors.Where(ctor => ctor != c1.PrimaryConstructor).All(ctor => ctor.GetAttributes().IsEmpty)); + + var c2 = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C2"); + Assert.Empty(c2.GetAttributes()); + Assert.True(c2.HasPrimaryConstructor); + Assert.Equal("B", c2.PrimaryConstructor.GetAttributes().Single().ToString()); + Assert.True(c2.Constructors.Where(ctor => ctor != c2.PrimaryConstructor).All(ctor => ctor.GetAttributes().IsEmpty)); + } + + [Fact] + public void AttributesOnPrimaryConstructor_10_NameofParameter() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ + public A(string x){} +} + +[method: A(nameof(someParam))] +class C(int someParam) +{ + int X = someParam; +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var c = (SourceNamedTypeSymbol)comp.GetTypeByMetadataName("C"); + Assert.Equal(@"A(""someParam"")", c.PrimaryConstructor.GetAttributes().Single().ToString()); + } + [Fact] public void AnalyzerActions_01_Class() { From 875514f29b2ef67d76b1d94daa8cedecda7bba2a Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 11 Apr 2023 08:15:28 -0700 Subject: [PATCH 16/69] Handle additional diagnostic scenarios for captured primary constructor parameters (#67712) Closes #67162. --- .../Portable/Binder/Binder_Expressions.cs | 43 ++- .../CSharp/Portable/CSharpResources.resx | 6 +- .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Source/SourceMemberContainerSymbol.cs | 8 + .../Portable/xlf/CSharpResources.cs.xlf | 10 +- .../Portable/xlf/CSharpResources.de.xlf | 10 +- .../Portable/xlf/CSharpResources.es.xlf | 10 +- .../Portable/xlf/CSharpResources.fr.xlf | 10 +- .../Portable/xlf/CSharpResources.it.xlf | 10 +- .../Portable/xlf/CSharpResources.ja.xlf | 10 +- .../Portable/xlf/CSharpResources.ko.xlf | 10 +- .../Portable/xlf/CSharpResources.pl.xlf | 10 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 +- .../Portable/xlf/CSharpResources.ru.xlf | 10 +- .../Portable/xlf/CSharpResources.tr.xlf | 10 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 +- .../Semantics/PrimaryConstructorTests.cs | 274 ++++++++++++++++++ 19 files changed, 390 insertions(+), 73 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 664e527c2c341..c4b5a5137b516 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1776,7 +1776,7 @@ private BoundExpression SynthesizeMethodGroupReceiver(CSharpSyntaxNode syntax, A private bool IsBadLocalOrParameterCapture(Symbol symbol, TypeSymbol type, RefKind refKind) { - if (refKind != RefKind.None || type.IsRefLikeType) + if (refKind != RefKind.None || type.IsRestrictedType()) { var containingMethod = this.ContainingMemberOrLambda as MethodSymbol; if ((object)containingMethod != null && (object)symbol.ContainingSymbol != (object)containingMethod) @@ -1899,7 +1899,15 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind if (IsBadLocalOrParameterCapture(localSymbol, type, localSymbol.RefKind)) { isError = true; - Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUseLocal, node, localSymbol); + + if (localSymbol.RefKind == RefKind.None && type.IsRestrictedType(ignoreSpanLikeTypes: true)) + { + Error(diagnostics, ErrorCode.ERR_SpecialByRefInLambda, node, type); + } + else + { + Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUseLocal, node, localSymbol); + } } } @@ -1931,7 +1939,20 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind if (IsBadLocalOrParameterCapture(parameter, parameter.Type, parameter.RefKind)) { isError = true; - Error(diagnostics, parameter.Type.IsRefLikeType ? ErrorCode.ERR_AnonDelegateCantUseRefLike : ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name); + + if (parameter.RefKind != RefKind.None) + { + Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name); + } + else if (parameter.Type.IsRestrictedType(ignoreSpanLikeTypes: true)) + { + Error(diagnostics, ErrorCode.ERR_SpecialByRefInLambda, node, parameter.Type); + } + else + { + Debug.Assert(parameter.Type.IsRefLikeType); + Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUseRefLike, node, parameter.Name); + } } else if (primaryCtor is not null) { @@ -1939,10 +1960,22 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind bool capture = (this.ContainingMember() is MethodSymbol containingMethod && (object)primaryCtor != containingMethod); if (capture && - (parameter.RefKind != RefKind.None || parameter.Type.IsRefLikeType) && + (parameter.RefKind != RefKind.None || parameter.Type.IsRestrictedType()) && !IsInsideNameof) { - Error(diagnostics, parameter.Type.IsRefLikeType ? ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefLike : ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRef, node, parameter.Name); + if (parameter.RefKind != RefKind.None) + { + Error(diagnostics, ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRef, node, parameter.Name); + } + else if (parameter.Type.IsRestrictedType(ignoreSpanLikeTypes: true)) + { + Error(diagnostics, ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefAny, node, parameter.Type); + } + else + { + Debug.Assert(parameter.Type.IsRefLikeType); + Error(diagnostics, ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefLike, node, parameter.Name); + } } else if (primaryCtor is { ThisParameter.RefKind: not RefKind.None } && this.ContainingMemberOrLambda is MethodSymbol { MethodKind: MethodKind.AnonymousFunction or MethodKind.LocalFunction } && diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index b36398e1d8b8e..7b3d76e2f2759 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5074,9 +5074,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Cannot return '{0}' by reference because it is a '{1}' - - Cannot return fields of '{0}' by reference because it is a '{1}' - A readonly field cannot be returned by writable reference @@ -7508,4 +7505,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A constant value of type '{0}' is expected + + Cannot use primary constructor parameter of type '{0}' inside an instance member + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 8423565e79902..ca89dd2af9635 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2190,6 +2190,7 @@ internal enum ErrorCode ERR_BadCaseInSwitchArm = 9134, ERR_ConstantValueOfTypeExpected = 9135, + ERR_UnsupportedPrimaryConstructorParameterCapturingRefAny = 9136, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 3052e5e6cf4f6..beaf7cfeab2db 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2310,6 +2310,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_BadStaticAfterUnsafe: case ErrorCode.ERR_BadCaseInSwitchArm: case ErrorCode.ERR_ConstantValueOfTypeExpected: + case ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefAny: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 75a8d2b16f2c2..589b61065b511 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2655,6 +2655,14 @@ private void CheckSequentialOnPartialType(BindingDiagnosticBag diagnostics) } } } + + if (whereFoundField != null && + PrimaryConstructor is { } primaryConstructor && primaryConstructor.GetCapturedParameters().Any() && + (primaryConstructor.SyntaxRef.SyntaxTree != whereFoundField.SyntaxTree || primaryConstructor.SyntaxRef.Span != whereFoundField.Span)) + { + diagnostics.Add(ErrorCode.WRN_SequentialOnPartialClass, GetFirstLocation(), this); + return; + } } private static bool HasInstanceData(MemberDeclarationSyntax m) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 08b608bf72086..9eec4895c74af 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1777,6 +1777,11 @@ Uvnitř členu instance nejde použít parametr primárního konstruktoru {0} typu odkaz, výstup nebo vstup. + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Nejde použít parametr primárního konstruktoru {0}, který má uvnitř členu instance typ podobný odkazu. @@ -11672,11 +11677,6 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference {0} nejde vrátit pomocí odkazu, protože je to {1}. - - Cannot return fields of '{0}' by reference because it is a '{1}' - Pole elementu {0} nejde vrátit pomocí odkazu, protože je to {1}. - - A readonly field cannot be returned by writable reference Pole jen pro čtení nejde vrátit zapisovatelným odkazem. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 1d6e11204d64a..f419ece90e2c2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1777,6 +1777,11 @@ Der ref-, out- oder in-primäre Konstruktorparameter „{0}“ kann nicht innerhalb eines Instanzmembers verwendet werden. + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Der primäre Konstruktorparameter „{0}“, der einen ref-ähnlichen Typ innerhalb eines Instanzmembers aufweist, kann nicht verwendet werden. @@ -11672,11 +11677,6 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett "{0}" kann nicht als Verweis zurückgegeben werden, weil es sich um ein {1}-Element handelt. - - Cannot return fields of '{0}' by reference because it is a '{1}' - Felder von "{0}" können nicht als Verweis zurückgegeben werden, weil es sich um ein {1}-Element handelt. - - A readonly field cannot be returned by writable reference Ein schreibgeschütztes Feld kann nicht als schreibbarer Verweis zurückgegeben werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 4f8f403b1ec52..0434ca386a6b7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1777,6 +1777,11 @@ No se puede usar ref, out o en el parámetro de constructor principal '{0}' dentro de un miembro de instancia + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member No se puede usar el parámetro de constructor principal '{0}' que tiene un tipo ref-like dentro de un miembro de instancia @@ -11672,11 +11677,6 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe '{0}' no se puede devolver por referencia porque es un '{1}'. - - Cannot return fields of '{0}' by reference because it is a '{1}' - Los campos de '{0}' no se pueden devolver por referencia porque es un '{1}'. - - A readonly field cannot be returned by writable reference No se puede devolver un campo de solo lectura por referencia grabable. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 67db681971e65..11a4c75e46504 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1777,6 +1777,11 @@ Impossible d’utiliser le paramètre de constructeur principal '{0}' avec ref, out ou in à l’intérieur d’un membre d’instance. + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Nous n’avons pas pu utiliser le paramètre de constructeur principal '{0}' qui a un type de référence similaire à l’intérieur d’un membre d’instance @@ -11672,11 +11677,6 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Impossible de retourner '{0}' par référence, car il s'agit d'un '{1}' - - Cannot return fields of '{0}' by reference because it is a '{1}' - Impossible de retourner les champs de '{0}' par référence, car il s'agit d'un '{1}' - - A readonly field cannot be returned by writable reference Impossible de retourner un champ readonly par référence accessible en écriture diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index fb63a0510b8cb..ca1540f042777 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1777,6 +1777,11 @@ Non è possibile usare il parametro ref, out o in del costruttore primario '{0}' all'interno di un membro di istanza + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Non è possibile usare il parametro del costruttore primario '{0}' con un tipo simile a ref all'interno di un membro di istanza @@ -11672,11 +11677,6 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Non è possibile restituire '{0}' per riferimento perché è '{1}' - - Cannot return fields of '{0}' by reference because it is a '{1}' - Non è possibile restituire i campi di '{0}' per riferimento perché è '{1}' - - A readonly field cannot be returned by writable reference Un campo di sola lettura non può restituito per riferimento scrivibile diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index e8beb6a35d68e..561713d66a430 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1777,6 +1777,11 @@ インスタンス メンバー内のプライマリ コンストラクター パラメーター '{0}' では ref、out、in を使用できません + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member インスタンス メンバー内に ref に似た型を持つプライマリ コンストラクター パラメーター '{0}' を使用することはできません @@ -11672,11 +11677,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ '{1}' であるため、'{0}' を参照渡しで返すことはできません - - Cannot return fields of '{0}' by reference because it is a '{1}' - '{1}' であるため、'{0}' のフィールドを参照渡しで返すことはできません - - A readonly field cannot be returned by writable reference 読み取り専用フィールドを書き込み可能な参照渡しで返すことはできません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index c480f2f4d0adc..128a8a2b7a1eb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1777,6 +1777,11 @@ 인스턴스 멤버 내에서 ref, out 또는 기본 생성자 '{0}' 매개 변수를 사용할 수 없습니다. + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member 인스턴스 멤버 내에 ref 유사 형식이 있는 기본 생성자 '{0}' 매개 변수를 사용할 수 없습니다. @@ -11671,11 +11676,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ '{0}'은(는) '{1}'이므로 참조로 반환할 수 없습니다. - - Cannot return fields of '{0}' by reference because it is a '{1}' - '{0}'의 필드는 '{1}'이므로 참조로 반환할 수 없습니다. - - A readonly field cannot be returned by writable reference 읽기 전용 필드는 쓰기 가능 참조로 반환될 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 3306e602d4082..c228fd971f016 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1777,6 +1777,11 @@ Nie można użyć ref, out lub w podstawowym parametrze konstruktora '{0}' wewnątrz elementu członkowskiego wystąpienia + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Nie można użyć podstawowego parametru konstruktora '{0}', który ma typ przypominający odwołanie wewnątrz składowej wystąpienia @@ -11672,11 +11677,6 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Nie można zwrócić elementu „{0}” przez referencję, ponieważ to jest element „{1}” - - Cannot return fields of '{0}' by reference because it is a '{1}' - Nie można zwrócić pól elementu „{0}” przez referencję, ponieważ to jest element „{1}” - - A readonly field cannot be returned by writable reference Nie można zwrócić pola tylko do odczytu przez zapisywalne odwołanie diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index c0e4438db70d8..3c63e31f63721 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1777,6 +1777,11 @@ Não é possível usar ref, out ou no parâmetro de construtor primário "{0}" dentro de um membro da instância + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Não é possível usar o parâmetro de construtor primário "{0}" que tem o tipo ref-like dentro de um membro de instância @@ -11672,11 +11677,6 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Não é possível retornar '{0}' por referência, porque ele é um '{1}' - - Cannot return fields of '{0}' by reference because it is a '{1}' - Não é possível retornar campos de '{0}' por referência, porque ele é um '{1}' - - A readonly field cannot be returned by writable reference Um campo somente leitura não pode ser retornado por referência gravável diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 1ef0d19b1ac13..a82d18a9c3940 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1777,6 +1777,11 @@ Невозможно использовать параметр основного конструктора "{0}" с модификаторами ref, out или in внутри элемента экземпляра + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Невозможно использовать параметр основного конструктора "{0}" типа ref-like внутри элемента экземпляра @@ -11672,11 +11677,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Невозможно вернуть "{0}" по ссылке, так как это "{1}" - - Cannot return fields of '{0}' by reference because it is a '{1}' - Невозможно вернуть поля "{0}" по ссылке, так как это "{1}" - - A readonly field cannot be returned by writable reference Поле, доступное только для чтения, невозможно вернуть по ссылке, доступной для записи diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 23c0e98db66e9..6ce875379dc5b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1777,6 +1777,11 @@ Bir örnek üye içinde ref, out veya in '{0}' birincil oluşturucu parametresi kullanılamaz + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member Bir örnek üye içinde ref benzeri türe sahip '{0}' birincil oluşturucu parametresi kullanılamaz @@ -11672,11 +11677,6 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T '{0}' öğesi bir '{1}' olduğundan başvuru ile döndürülemez - - Cannot return fields of '{0}' by reference because it is a '{1}' - '{0}' bir '{1}' olduğundan alanları başvuru ile döndürülemez - - A readonly field cannot be returned by writable reference Salt okunur bir alan, yazılabilir başvuru ile döndürülemez diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index cefa521ea09ed..cbfe6c7f76eb0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1777,6 +1777,11 @@ 无法在实例成员内的主构造函数参数 “{0}” 中使用 ref、out 或 + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member 无法使用实例成员内具有 ref-like 类型的主构造函数参数 “{0}” @@ -11677,11 +11682,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ “{0}”是一个“{1}”,无法通过引用返回 - - Cannot return fields of '{0}' by reference because it is a '{1}' - “{0}”是一个“{1}”,无法通过引用返回其字段 - - A readonly field cannot be returned by writable reference 只读字段无法通过可写的引用返回 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 3d3bdc4e84ed4..15c8a3eb6db39 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1777,6 +1777,11 @@ 無法在執行個體成員內使用 ref、out 或 in 主要建立建構函式參數 '{0}' + + Cannot use primary constructor parameter of type '{0}' inside an instance member + Cannot use primary constructor parameter of type '{0}' inside an instance member + + Cannot use primary constructor parameter '{0}' that has ref-like type inside an instance member 無法使用執行個體成員內具有類似參考類型的主要建立函式參數 '{0}' @@ -11672,11 +11677,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 無法藉傳址方式傳回 '{0}',因其為 '{1}' - - Cannot return fields of '{0}' by reference because it is a '{1}' - 無法藉傳址方式傳回 '{0}' 欄位,因其為 '{1}' - - A readonly field cannot be returned by writable reference 無法以可寫入傳址方式傳回唯讀欄位 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs index 835ac4a7eb55e..e3905450d2eff 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PrimaryConstructorTests.cs @@ -15794,5 +15794,279 @@ public S3(object o) : this() Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(6, 27) ); } + + [Fact] + public void StructLayout_01() + { + string source = @" +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Explicit)] +struct S(int x, int y) +{ + int X = x; + int Y => y; + + [FieldOffset(8)] + int Z = 0; +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // We might want to adjust the warning depending on what we decide to do for + // https://github.com/dotnet/csharplang/blob/main/proposals/primary-constructors.md#field-targeting-attributes-for-captured-primary-constructor-parameters. + // + // If we decide to support attributes for capture fields, consider testing + // ERR_MarshalUnmanagedTypeNotValidForFields + // ERR_StructOffsetOnBadStruct + // ERR_DoNotUseFixedBufferAttr + + // (5,21): error CS0625: 'S.P': instance field in types marked with StructLayout(LayoutKind.Explicit) must have a FieldOffset attribute + // struct S(int x, int y) + Diagnostic(ErrorCode.ERR_MissingStructOffset, "y").WithArguments("S.P").WithLocation(5, 21), + // (7,9): error CS0625: 'S.X': instance field in types marked with StructLayout(LayoutKind.Explicit) must have a FieldOffset attribute + // int X = x; + Diagnostic(ErrorCode.ERR_MissingStructOffset, "X").WithArguments("S.X").WithLocation(7, 9) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/67162")] + public void StructLayout_02() + { + string source1 = @" +public partial struct S(int x) +{ + int X => x; +} +"; + string source2 = @" +public partial struct S +{ + public int Y; +} +"; + verify1(source1, source2, validate2); + verify1(source1 + source2, "", validate2); + verify1(source2, source1, validate3); + verify1(source2 + source1, "", validate3); + + void verify1(string source1, string source2, Action validator) + { + var comp = CreateCompilation(new[] { source1, source2 }); + CompileAndVerify(comp, symbolValidator: validator, sourceSymbolValidator: validator).VerifyDiagnostics( + // 0.cs(2,23): warning CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'S'. To specify an ordering, all instance fields must be in the same declaration. + // public partial struct S(int x) + Diagnostic(ErrorCode.WRN_SequentialOnPartialClass, "S").WithArguments("S").WithLocation(2, 23) + ); + } + + void validate2(ModuleSymbol m) + { + var fields = m.GlobalNamespace.GetTypeMember("S").GetMembers().OfType().ToArray(); + Assert.Equal(2, fields.Length); + Assert.Equal("P", fields[0].Name); + Assert.Equal("Y", fields[1].Name); + } + + void validate3(ModuleSymbol m) + { + var fields = m.GlobalNamespace.GetTypeMember("S").GetMembers().OfType().ToArray(); + Assert.Equal(2, fields.Length); + Assert.Equal("Y", fields[0].Name); + Assert.Equal("P", fields[1].Name); + } + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/67162")] + public void StructLayout_03() + { + string source1 = @" +public partial struct S(int x) +{ +} +"; + string source2 = @" +public partial struct S +{ + int X = x; +} +"; + verify1(source1, source2, validate2); + verify1(source1 + source2, "", validate2); + verify1(source2, source1, validate2); + verify1(source2 + source1, "", validate2); + + void verify1(string source1, string source2, Action validator) + { + var comp = CreateCompilation(new[] { source1, source2 }); + CompileAndVerify(comp, symbolValidator: validator, sourceSymbolValidator: validator).VerifyDiagnostics(); + } + + void validate2(ModuleSymbol m) + { + Assert.Equal(1, m.GlobalNamespace.GetTypeMember("S").GetMembers().OfType().Count()); + } + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/67162")] + public void StructLayout_04() + { + string source1 = @" +public partial struct S(int x) +{ + int X => x; + public int Y; +} +"; + string source2 = @" +public partial struct S +{ +} +"; + verify1(source1, source2, validate2); + verify1(source1 + source2, "", validate2); + verify1(source2, source1, validate2); + verify1(source2 + source1, "", validate2); + + void verify1(string source1, string source2, Action validator) + { + var comp = CreateCompilation(new[] { source1, source2 }); + CompileAndVerify(comp, symbolValidator: validator, sourceSymbolValidator: validator).VerifyDiagnostics(); + } + + void validate2(ModuleSymbol m) + { + var fields = m.GlobalNamespace.GetTypeMember("S").GetMembers().OfType().ToArray(); + Assert.Equal(2, fields.Length); + Assert.Equal("P", fields[0].Name); + Assert.Equal("Y", fields[1].Name); + } + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/67162")] + public void StructLayout_05() + { + string source1 = @" +public partial struct S(int x) +{ + int X => x; +} +"; + string source2 = @" +public partial struct S +{ + public int Y; +} +"; + string source3 = @" +public partial struct S +{ + public int Z; +} +"; + verify1(source1, source2, source3); + verify1(source1 + source2, source3, ""); + verify1(source1 + source2 + source3, "", ""); + + verify1(source1, source3, source2); + verify1(source1 + source3, source2, ""); + verify1(source1 + source3 + source2, "", ""); + + verify1(source2, source1, source3); + verify1(source2 + source1, source3, ""); + verify1(source2 + source1 + source3, "", ""); + + verify1(source2, source3, source1); + verify1(source2 + source3, source1, ""); + verify1(source2 + source3 + source1, "", ""); + + verify1(source3, source1, source2); + verify1(source3 + source1, source2, ""); + verify1(source3 + source1 + source2, "", ""); + + verify1(source3, source2, source1); + verify1(source3 + source2, source1, ""); + verify1(source3 + source2 + source1, "", ""); + + void verify1(string source1, string source2, string source3) + { + var comp = CreateCompilation(new[] { source1, source2, source3 }); + comp.VerifyDiagnostics( + // 0.cs(2,23): warning CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'S'. To specify an ordering, all instance fields must be in the same declaration. + // public partial struct S(int x) + Diagnostic(ErrorCode.WRN_SequentialOnPartialClass, "S").WithArguments("S").WithLocation(2, 23) + ); + } + } + + [Theory] + [CombinatorialData] + public void RestrictedType_01([CombinatorialValues("class", "struct")] string declaration) + { + var src1 = @" +" + declaration + @" C1 +(System.ArgIterator a) +{ + void M() + { + _ = a; + } +} + +" + declaration + @" C2 +(System.ArgIterator b) +{ + void M() + { + System.Action d = () => _ = b; + } +} + +" + declaration + @" C3 +(System.ArgIterator c) +{ + System.Action d = () => _ = c; +} + +#pragma warning disable CS" + UnreadParameterWarning() + @" // Parameter 'z' is unread. +" + declaration + @" C4(System.ArgIterator z) +{ +} +"; + var comp = CreateCompilation(src1, targetFramework: TargetFramework.DesktopLatestExtended); + comp.VerifyDiagnostics( + // (7,13): error CS9136: Cannot use primary constructor parameter of type 'ArgIterator' inside an instance member + // _ = a; + Diagnostic(ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefAny, "a").WithArguments("System.ArgIterator").WithLocation(7, 13), + // (16,37): error CS4013: Instance of type 'ArgIterator' cannot be used inside a nested function, query expression, iterator block or async method + // System.Action d = () => _ = b; + Diagnostic(ErrorCode.ERR_SpecialByRefInLambda, "b").WithArguments("System.ArgIterator").WithLocation(16, 37), + // (23,33): error CS4013: Instance of type 'ArgIterator' cannot be used inside a nested function, query expression, iterator block or async method + // System.Action d = () => _ = c; + Diagnostic(ErrorCode.ERR_SpecialByRefInLambda, "c").WithArguments("System.ArgIterator").WithLocation(23, 33) + ); + } + + [Theory] + [CombinatorialData] + public void RestrictedType_02([CombinatorialValues("record", "record class", "record struct")] string keyword) + { + var src1 = @" +" + keyword + @" C1 +(System.ArgIterator x) +{ +} +"; + var comp = CreateCompilation(src1, targetFramework: TargetFramework.NetCoreApp); + comp.VerifyDiagnostics( + // (3,2): error CS0610: Field or property cannot be of type 'ArgIterator' + // (System.ArgIterator x) + Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(3, 2) + ); + } } } From 41caeb0f8fe87061fd4495403c53a3e33de41ced Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Tue, 11 Apr 2023 10:41:38 -0500 Subject: [PATCH 17/69] Restore null check --- ...olUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs index aae5710b03139..fa713fa175768 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs @@ -444,7 +444,10 @@ public void SetAnalysisDataOnMethodExit() parameter, static (write, arg) => { - arg.self.SymbolsWriteBuilder[(arg.parameter, write)] = true; + if (write != null) + { + arg.self.SymbolsWriteBuilder[(arg.parameter, write)] = true; + } }, (parameter, self: this)); } From c60352395a8024440758cde4b0b3cb4e8cd83d2e Mon Sep 17 00:00:00 2001 From: gzatravkin <53317567+gzatravkin@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:14:04 -0300 Subject: [PATCH 18/69] Fixes imposibility of Edit and continue for attributes with type (#67704) It was comparing type via object.Equals instead of TypesEquivalent. Changing it so you can Edit and continue if type still match. --- .../EditAndContinue/TopLevelEditingTests.cs | 26 +++++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 1 + 2 files changed, 27 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 796242b03fa92..7140773c06edc 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -8281,6 +8281,32 @@ static void Main(string[] args) capabilities: EditAndContinueCapabilities.Baseline); } + [Fact] + public void MethodUpdate_AttributeWithTypeAtConstructor() + { + var src1 = "using System; [AttributeUsage(AttributeTargets.All)] public class AAttribute : Attribute { public AAttribute(Type t) { } } class C { [A(typeof(C))] public void M() { Console.WriteLine(\"2\"); } }"; + var src2 = "using System; [AttributeUsage(AttributeTargets.All)] public class AAttribute : Attribute { public AAttribute(Type t) { } } class C { [A(typeof(C))] public void M() { Console.WriteLine(\"1\"); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(@"Update [[A(typeof(C))] public void M() { Console.WriteLine(""2""); }]@133 -> [[A(typeof(C))] public void M() { Console.WriteLine(""1""); }]@133"); + + edits.VerifySemanticDiagnostics(); + } + + [Fact] + public void MethodUpdate_AttributeWithTypeAtConstructor2() + { + var src1 = "using System; [AttributeUsage(AttributeTargets.All)] public class AAttribute : Attribute { public AAttribute(Type t) { } } class C { [A(typeof(object))] public void M() { Console.WriteLine(\"2\"); } }"; + var src2 = "using System; [AttributeUsage(AttributeTargets.All)] public class AAttribute : Attribute { public AAttribute(Type t) { } } class C { [A(typeof(dynamic))] public void M() { Console.WriteLine(\"1\"); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(@"Update [[A(typeof(object))] public void M() { Console.WriteLine(""2""); }]@133 -> [[A(typeof(dynamic))] public void M() { Console.WriteLine(""1""); }]@133"); + + edits.VerifySemanticDiagnostics(); + } + [Fact] public void MethodUpdate_AddAttribute_SupportedByRuntime() { diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 4a4262f57a6aa..5cce53b0c9bd7 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -6184,6 +6184,7 @@ public bool Equals(TypedConstant x, TypedConstant y) x.Kind switch { TypedConstantKind.Array => x.Values.SequenceEqual(y.Values, TypedConstantComparer.Instance), + TypedConstantKind.Type => TypesEquivalent(x.Value as ITypeSymbol, y.Value as ITypeSymbol, exact: false), _ => object.Equals(x.Value, y.Value) }; From 5a60717018d1e2d20b4c61214c60873ba7100ddb Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 11 Apr 2023 13:39:05 -0700 Subject: [PATCH 19/69] Move to a SegmentedHashSet from a SegmentedDictionary (#67718) * Move to a SegmentedHashSet from a SegmentedDictionary * Remove extensions --- .../Portable/Compilation/CSharpCompilation.cs | 2 +- .../Portable/Declarations/DeclarationTable.cs | 4 +- .../Declarations/DeclarationTreeBuilder.cs | 46 +++++++++---------- .../Declarations/MergedTypeDeclaration.cs | 2 +- .../Declarations/SingleTypeDeclaration.cs | 4 +- ...bleSegmentedDictionaryBuilderExtensions.cs | 28 ----------- 6 files changed, 29 insertions(+), 57 deletions(-) delete mode 100644 src/Compilers/Core/Portable/Collections/ImmutableSegmentedDictionaryBuilderExtensions.cs diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 078a52970ea02..5f5c1a4c3d414 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -4744,7 +4744,7 @@ protected override bool ShouldCheckTypeForMembers(MergedTypeDeclaration current) { foreach (SingleTypeDeclaration typeDecl in current.Declarations) { - if (typeDecl.MemberNames.ContainsKey(_name)) + if (typeDecl.MemberNames.Contains(_name)) { return true; } diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs index a811203ee22bf..dd991cf527d7d 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTable.cs @@ -281,7 +281,7 @@ public static bool ContainsName( mergedRoot, n => n == name, filter, - t => t.MemberNames.ContainsKey(name), + t => t.MemberNames.Contains(name), cancellationToken); } @@ -295,7 +295,7 @@ public static bool ContainsName( mergedRoot, predicate, filter, t => { - foreach (var (name, _) in t.MemberNames) + foreach (var name in t.MemberNames) { if (predicate(name)) { diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs index 96b75c66a830d..dcffaa303c257 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs @@ -138,7 +138,7 @@ private ImmutableArray VisitNamespaceChildren( return childrenBuilder.ToImmutableAndFree(); } - private static SingleNamespaceOrTypeDeclaration CreateImplicitClass(ImmutableSegmentedDictionary memberNames, SyntaxReference container, SingleTypeDeclaration.TypeDeclarationFlags declFlags) + private static SingleNamespaceOrTypeDeclaration CreateImplicitClass(ImmutableSegmentedHashSet memberNames, SyntaxReference container, SingleTypeDeclaration.TypeDeclarationFlags declFlags) { return new SingleTypeDeclaration( kind: DeclarationKind.ImplicitClass, @@ -167,7 +167,7 @@ private static SingleNamespaceOrTypeDeclaration CreateSimpleProgram(GlobalStatem SingleTypeDeclaration.TypeDeclarationFlags.IsSimpleProgram, syntaxReference: firstGlobalStatement.SyntaxTree.GetReference(firstGlobalStatement.Parent), nameLocation: new SourceLocation(firstGlobalStatement.GetFirstToken()), - memberNames: ImmutableSegmentedDictionary.Empty, + memberNames: ImmutableSegmentedHashSet.Empty, children: ImmutableArray.Empty, diagnostics: diagnostics, quickAttributes: QuickAttributes.None); @@ -236,7 +236,7 @@ private static ImmutableArray GetReferenceDirectives(Compila private SingleNamespaceOrTypeDeclaration CreateScriptClass( CompilationUnitSyntax parent, ImmutableArray children, - ImmutableSegmentedDictionary memberNames, + ImmutableSegmentedHashSet memberNames, SingleTypeDeclaration.TypeDeclarationFlags declFlags) { Debug.Assert(parent.Kind() == SyntaxKind.CompilationUnit && _syntaxTree.Options.Kind != SourceCodeKind.Regular); @@ -728,7 +728,7 @@ public override SingleNamespaceOrTypeDeclaration VisitDelegateDeclaration(Delega declFlags: declFlags, syntaxReference: _syntaxTree.GetReference(node), nameLocation: new SourceLocation(node.Identifier), - memberNames: ImmutableSegmentedDictionary.Empty, + memberNames: ImmutableSegmentedHashSet.Empty, children: ImmutableArray.Empty, diagnostics: diagnostics.ToReadOnlyAndFree(), _nonGlobalAliasedQuickAttributes | quickAttributes); @@ -747,7 +747,7 @@ public override SingleNamespaceOrTypeDeclaration VisitEnumDeclaration(EnumDeclar declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasBaseDeclarations; } - ImmutableSegmentedDictionary memberNames = GetEnumMemberNames(members, ref declFlags); + ImmutableSegmentedHashSet memberNames = GetEnumMemberNames(members, ref declFlags); var diagnostics = DiagnosticBag.GetInstance(); var modifiers = node.Modifiers.ToDeclarationModifiers(isForTypeDeclaration: true, diagnostics: diagnostics); @@ -786,10 +786,10 @@ private static QuickAttributes GetQuickAttributes(SyntaxList.Builder> s_memberNameBuilderPool = - new ObjectPool.Builder>(() => ImmutableSegmentedDictionary.CreateBuilder()); + private static readonly ObjectPool.Builder> s_memberNameBuilderPool = + new ObjectPool.Builder>(() => ImmutableSegmentedHashSet.CreateBuilder()); - private static ImmutableSegmentedDictionary ToImmutableAndFree(ImmutableSegmentedDictionary.Builder builder) + private static ImmutableSegmentedHashSet ToImmutableAndFree(ImmutableSegmentedHashSet.Builder builder) { var result = builder.ToImmutable(); builder.Clear(); @@ -797,7 +797,7 @@ private static ImmutableSegmentedDictionary ToImmutableAndFr return result; } - private static ImmutableSegmentedDictionary GetEnumMemberNames(SeparatedSyntaxList members, ref SingleTypeDeclaration.TypeDeclarationFlags declFlags) + private static ImmutableSegmentedHashSet GetEnumMemberNames(SeparatedSyntaxList members, ref SingleTypeDeclaration.TypeDeclarationFlags declFlags) { var cnt = members.Count; @@ -810,7 +810,7 @@ private static ImmutableSegmentedDictionary GetEnumMemberNam bool anyMemberHasAttributes = false; foreach (var member in members) { - memberNamesBuilder.TryAdd(member.Identifier.ValueText); + memberNamesBuilder.Add(member.Identifier.ValueText); if (!anyMemberHasAttributes && member.AttributeLists.Any()) { anyMemberHasAttributes = true; @@ -825,7 +825,7 @@ private static ImmutableSegmentedDictionary GetEnumMemberNam return ToImmutableAndFree(memberNamesBuilder); } - private static ImmutableSegmentedDictionary GetNonTypeMemberNames( + private static ImmutableSegmentedHashSet GetNonTypeMemberNames( CoreInternalSyntax.SyntaxList members, ref SingleTypeDeclaration.TypeDeclarationFlags declFlags, bool skipGlobalStatements = false, bool hasPrimaryCtor = false) { bool anyMethodHadExtensionSyntax = false; @@ -837,7 +837,7 @@ private static ImmutableSegmentedDictionary GetNonTypeMember if (hasPrimaryCtor) { - memberNameBuilder.TryAdd(WellKnownMemberNames.InstanceConstructorName); + memberNameBuilder.Add(WellKnownMemberNames.InstanceConstructorName); } foreach (var member in members) @@ -975,7 +975,7 @@ private static bool CheckMemberForAttributes(Syntax.InternalSyntax.CSharpSyntaxN } private static void AddNonTypeMemberNames( - Syntax.InternalSyntax.CSharpSyntaxNode member, ImmutableSegmentedDictionary.Builder set, ref bool anyNonTypeMembers, bool skipGlobalStatements) + Syntax.InternalSyntax.CSharpSyntaxNode member, ImmutableSegmentedHashSet.Builder set, ref bool anyNonTypeMembers, bool skipGlobalStatements) { switch (member.Kind) { @@ -986,7 +986,7 @@ private static void AddNonTypeMemberNames( int numFieldDeclarators = fieldDeclarators.Count; for (int i = 0; i < numFieldDeclarators; i++) { - set.TryAdd(fieldDeclarators[i].Identifier.ValueText); + set.Add(fieldDeclarators[i].Identifier.ValueText); } break; @@ -997,7 +997,7 @@ private static void AddNonTypeMemberNames( int numEventDeclarators = eventDeclarators.Count; for (int i = 0; i < numEventDeclarators; i++) { - set.TryAdd(eventDeclarators[i].Identifier.ValueText); + set.Add(eventDeclarators[i].Identifier.ValueText); } break; @@ -1010,7 +1010,7 @@ private static void AddNonTypeMemberNames( var methodDecl = (Syntax.InternalSyntax.MethodDeclarationSyntax)member; if (methodDecl.ExplicitInterfaceSpecifier == null) { - set.TryAdd(methodDecl.Identifier.ValueText); + set.Add(methodDecl.Identifier.ValueText); } break; @@ -1020,7 +1020,7 @@ private static void AddNonTypeMemberNames( var propertyDecl = (Syntax.InternalSyntax.PropertyDeclarationSyntax)member; if (propertyDecl.ExplicitInterfaceSpecifier == null) { - set.TryAdd(propertyDecl.Identifier.ValueText); + set.Add(propertyDecl.Identifier.ValueText); } break; @@ -1030,25 +1030,25 @@ private static void AddNonTypeMemberNames( var eventDecl = (Syntax.InternalSyntax.EventDeclarationSyntax)member; if (eventDecl.ExplicitInterfaceSpecifier == null) { - set.TryAdd(eventDecl.Identifier.ValueText); + set.Add(eventDecl.Identifier.ValueText); } break; case SyntaxKind.ConstructorDeclaration: anyNonTypeMembers = true; - set.TryAdd(((Syntax.InternalSyntax.ConstructorDeclarationSyntax)member).Modifiers.Any((int)SyntaxKind.StaticKeyword) + set.Add(((Syntax.InternalSyntax.ConstructorDeclarationSyntax)member).Modifiers.Any((int)SyntaxKind.StaticKeyword) ? WellKnownMemberNames.StaticConstructorName : WellKnownMemberNames.InstanceConstructorName); break; case SyntaxKind.DestructorDeclaration: anyNonTypeMembers = true; - set.TryAdd(WellKnownMemberNames.DestructorName); + set.Add(WellKnownMemberNames.DestructorName); break; case SyntaxKind.IndexerDeclaration: anyNonTypeMembers = true; - set.TryAdd(WellKnownMemberNames.Indexer); + set.Add(WellKnownMemberNames.Indexer); break; case SyntaxKind.OperatorDeclaration: @@ -1061,7 +1061,7 @@ private static void AddNonTypeMemberNames( if (opDecl.ExplicitInterfaceSpecifier == null) { var name = OperatorFacts.OperatorNameFromDeclaration(opDecl); - set.TryAdd(name); + set.Add(name); } } break; @@ -1076,7 +1076,7 @@ private static void AddNonTypeMemberNames( if (opDecl.ExplicitInterfaceSpecifier == null) { var name = OperatorFacts.OperatorNameFromDeclaration(opDecl); - set.TryAdd(name); + set.Add(name); } } break; diff --git a/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs index 966cbae8d051f..e55a01689cfc3 100644 --- a/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/MergedTypeDeclaration.cs @@ -243,7 +243,7 @@ public ICollection MemberNames { if (_lazyMemberNames == null) { - var names = UnionCollection.Create(this.Declarations, d => d.MemberNames.Keys); + var names = UnionCollection.Create(this.Declarations, d => d.MemberNames); Interlocked.CompareExchange(ref _lazyMemberNames, names, null); } diff --git a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs index 1110313132338..8608c0caaafe5 100644 --- a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs @@ -70,7 +70,7 @@ internal SingleTypeDeclaration( TypeDeclarationFlags declFlags, SyntaxReference syntaxReference, SourceLocation nameLocation, - ImmutableSegmentedDictionary memberNames, + ImmutableSegmentedHashSet memberNames, ImmutableArray children, ImmutableArray diagnostics, QuickAttributes quickAttributes) @@ -119,7 +119,7 @@ public DeclarationModifiers Modifiers } } - public ImmutableSegmentedDictionary MemberNames { get; } + public ImmutableSegmentedHashSet MemberNames { get; } public bool AnyMemberHasExtensionMethodSyntax { diff --git a/src/Compilers/Core/Portable/Collections/ImmutableSegmentedDictionaryBuilderExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableSegmentedDictionaryBuilderExtensions.cs deleted file mode 100644 index 5b8ee123d6156..0000000000000 --- a/src/Compilers/Core/Portable/Collections/ImmutableSegmentedDictionaryBuilderExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Collections -{ - internal static class ImmutableSegmentedDictionaryBuilderExtensions - { - public static bool TryAdd( - this ImmutableSegmentedDictionary.Builder dictionary, - T value) - where T : notnull - { -#if NETCOREAPP - return dictionary.TryAdd(value, default); -#else - if (dictionary.ContainsKey(value)) - return false; - - dictionary[value] = default; - return true; -#endif - } - } -} From 86e1452fa61ecd923aae106c25ec61c4de30e1c3 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 11 Apr 2023 17:12:20 -0700 Subject: [PATCH 20/69] Cache AssemblyIdentity instances produced in compiler (#67749) --- .../AssemblyIdentity.DisplayName.cs | 346 ++++++++++-------- 1 file changed, 187 insertions(+), 159 deletions(-) diff --git a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs index d33ece993669c..9ecff6add2314 100644 --- a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs +++ b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentity.DisplayName.cs @@ -162,6 +162,18 @@ public static bool TryParseDisplayName(string displayName, [NotNullWhen(true)] o return TryParseDisplayName(displayName, out identity, parts: out _); } + /// + /// Perf: ETW traces show 2%+ of all allocations parsing assembly identity names. This is due to how large + /// these strings can be (600+ characters in some cases), and how many substrings are continually produced as + /// the string is broken up into the pieces needed by AssemblyIdentity. The capacity of 1024 was picked as + /// around 700 unique strings were found in a solution the size of Roslyn.sln. So this seems like a reasonable + /// starting point for a large solution. This cache takes up around 240k in memory, but ends up saving >80MB of + /// garbage over typing even a few characters. And, of course, that savings just grows over the lifetime of a + /// session this is hosted within. + /// + private static readonly ConcurrentCache s_TryParseDisplayNameCache = + new ConcurrentCache(1024, ReferenceEqualityComparer.Instance); + /// /// Parses display name filling defaults for any basic properties that are missing. /// @@ -181,239 +193,255 @@ public static bool TryParseDisplayName(string displayName, [NotNullWhen(true)] o /// is null. public static bool TryParseDisplayName(string displayName, [NotNullWhen(true)] out AssemblyIdentity? identity, out AssemblyIdentityParts parts) { - // see ndp\clr\src\Binder\TextualIdentityParser.cpp, ndp\clr\src\Binder\StringLexer.cpp - - identity = null; - parts = 0; - - if (displayName == null) + if (!s_TryParseDisplayNameCache.TryGetValue(displayName, out var identityAndParts)) { - throw new ArgumentNullException(nameof(displayName)); + if (tryParseDisplayName(displayName, out var localIdentity, out var localParts)) + { + identityAndParts = (localIdentity, localParts); + s_TryParseDisplayNameCache.TryAdd(displayName, identityAndParts); + } } - if (displayName.IndexOf('\0') >= 0) - { - return false; - } + identity = identityAndParts.identity; + parts = identityAndParts.parts; + return identity != null; - int position = 0; - string? simpleName; - if (!TryParseNameToken(displayName, ref position, out simpleName)) + static bool tryParseDisplayName(string displayName, [NotNullWhen(true)] out AssemblyIdentity? identity, out AssemblyIdentityParts parts) { - return false; - } - - var parsedParts = AssemblyIdentityParts.Name; - var seen = AssemblyIdentityParts.Name; + // see ndp\clr\src\Binder\TextualIdentityParser.cpp, ndp\clr\src\Binder\StringLexer.cpp - Version? version = null; - string? culture = null; - bool isRetargetable = false; - var contentType = AssemblyContentType.Default; - var publicKey = default(ImmutableArray); - var publicKeyToken = default(ImmutableArray); + identity = null; + parts = 0; - while (position < displayName.Length) - { - // Parse ',' name '=' value - if (displayName[position] != ',') + if (displayName == null) { - return false; + throw new ArgumentNullException(nameof(displayName)); } - position++; - - string? propertyName; - if (!TryParseNameToken(displayName, ref position, out propertyName)) + if (displayName.IndexOf('\0') >= 0) { return false; } - if (position >= displayName.Length || displayName[position] != '=') + int position = 0; + string? simpleName; + if (!TryParseNameToken(displayName, ref position, out simpleName)) { return false; } - position++; + var parsedParts = AssemblyIdentityParts.Name; + var seen = AssemblyIdentityParts.Name; - string? propertyValue; - if (!TryParseNameToken(displayName, ref position, out propertyValue)) - { - return false; - } + Version? version = null; + string? culture = null; + bool isRetargetable = false; + var contentType = AssemblyContentType.Default; + var publicKey = default(ImmutableArray); + var publicKeyToken = default(ImmutableArray); - // Process property - if (string.Equals(propertyName, "Version", StringComparison.OrdinalIgnoreCase)) + while (position < displayName.Length) { - if ((seen & AssemblyIdentityParts.Version) != 0) + // Parse ',' name '=' value + if (displayName[position] != ',') { return false; } - seen |= AssemblyIdentityParts.Version; + position++; - if (propertyValue == "*") - { - continue; - } - - ulong versionLong; - AssemblyIdentityParts versionParts; - if (!TryParseVersion(propertyValue, out versionLong, out versionParts)) + string? propertyName; + if (!TryParseNameToken(displayName, ref position, out propertyName)) { return false; } - version = ToVersion(versionLong); - parsedParts |= versionParts; - } - else if (string.Equals(propertyName, "Culture", StringComparison.OrdinalIgnoreCase) || - string.Equals(propertyName, "Language", StringComparison.OrdinalIgnoreCase)) - { - if ((seen & AssemblyIdentityParts.Culture) != 0) + if (position >= displayName.Length || displayName[position] != '=') { return false; } - seen |= AssemblyIdentityParts.Culture; + position++; - if (propertyValue == "*") + string? propertyValue; + if (!TryParseNameToken(displayName, ref position, out propertyValue)) { - continue; + return false; } - culture = string.Equals(propertyValue, InvariantCultureDisplay, StringComparison.OrdinalIgnoreCase) ? null : propertyValue; - parsedParts |= AssemblyIdentityParts.Culture; - } - else if (string.Equals(propertyName, "PublicKey", StringComparison.OrdinalIgnoreCase)) - { - if ((seen & AssemblyIdentityParts.PublicKey) != 0) + // Process property + if (string.Equals(propertyName, "Version", StringComparison.OrdinalIgnoreCase)) { - return false; - } + if ((seen & AssemblyIdentityParts.Version) != 0) + { + return false; + } - seen |= AssemblyIdentityParts.PublicKey; + seen |= AssemblyIdentityParts.Version; - if (propertyValue == "*") - { - continue; - } + if (propertyValue == "*") + { + continue; + } - ImmutableArray value; - if (!TryParsePublicKey(propertyValue, out value)) - { - return false; + ulong versionLong; + AssemblyIdentityParts versionParts; + if (!TryParseVersion(propertyValue, out versionLong, out versionParts)) + { + return false; + } + + version = ToVersion(versionLong); + parsedParts |= versionParts; } + else if (string.Equals(propertyName, "Culture", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "Language", StringComparison.OrdinalIgnoreCase)) + { + if ((seen & AssemblyIdentityParts.Culture) != 0) + { + return false; + } - // NOTE: Fusion would also set the public key token (as derived from the public key) here. - // We may need to do this as well for error cases, as Fusion would fail to parse the - // assembly name if public key token calculation failed. + seen |= AssemblyIdentityParts.Culture; - publicKey = value; - parsedParts |= AssemblyIdentityParts.PublicKey; - } - else if (string.Equals(propertyName, "PublicKeyToken", StringComparison.OrdinalIgnoreCase)) - { - if ((seen & AssemblyIdentityParts.PublicKeyToken) != 0) - { - return false; + if (propertyValue == "*") + { + continue; + } + + culture = string.Equals(propertyValue, InvariantCultureDisplay, StringComparison.OrdinalIgnoreCase) ? null : propertyValue; + parsedParts |= AssemblyIdentityParts.Culture; } + else if (string.Equals(propertyName, "PublicKey", StringComparison.OrdinalIgnoreCase)) + { + if ((seen & AssemblyIdentityParts.PublicKey) != 0) + { + return false; + } - seen |= AssemblyIdentityParts.PublicKeyToken; + seen |= AssemblyIdentityParts.PublicKey; - if (propertyValue == "*") - { - continue; - } + if (propertyValue == "*") + { + continue; + } - ImmutableArray value; - if (!TryParsePublicKeyToken(propertyValue, out value)) - { - return false; - } + ImmutableArray value; + if (!TryParsePublicKey(propertyValue, out value)) + { + return false; + } - publicKeyToken = value; - parsedParts |= AssemblyIdentityParts.PublicKeyToken; - } - else if (string.Equals(propertyName, "Retargetable", StringComparison.OrdinalIgnoreCase)) - { - if ((seen & AssemblyIdentityParts.Retargetability) != 0) - { - return false; + // NOTE: Fusion would also set the public key token (as derived from the public key) here. + // We may need to do this as well for error cases, as Fusion would fail to parse the + // assembly name if public key token calculation failed. + + publicKey = value; + parsedParts |= AssemblyIdentityParts.PublicKey; } + else if (string.Equals(propertyName, "PublicKeyToken", StringComparison.OrdinalIgnoreCase)) + { + if ((seen & AssemblyIdentityParts.PublicKeyToken) != 0) + { + return false; + } - seen |= AssemblyIdentityParts.Retargetability; + seen |= AssemblyIdentityParts.PublicKeyToken; - if (propertyValue == "*") - { - continue; - } + if (propertyValue == "*") + { + continue; + } - if (string.Equals(propertyValue, "Yes", StringComparison.OrdinalIgnoreCase)) - { - isRetargetable = true; + ImmutableArray value; + if (!TryParsePublicKeyToken(propertyValue, out value)) + { + return false; + } + + publicKeyToken = value; + parsedParts |= AssemblyIdentityParts.PublicKeyToken; } - else if (string.Equals(propertyValue, "No", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(propertyName, "Retargetable", StringComparison.OrdinalIgnoreCase)) { - isRetargetable = false; + if ((seen & AssemblyIdentityParts.Retargetability) != 0) + { + return false; + } + + seen |= AssemblyIdentityParts.Retargetability; + + if (propertyValue == "*") + { + continue; + } + + if (string.Equals(propertyValue, "Yes", StringComparison.OrdinalIgnoreCase)) + { + isRetargetable = true; + } + else if (string.Equals(propertyValue, "No", StringComparison.OrdinalIgnoreCase)) + { + isRetargetable = false; + } + else + { + return false; + } + + parsedParts |= AssemblyIdentityParts.Retargetability; } - else + else if (string.Equals(propertyName, "ContentType", StringComparison.OrdinalIgnoreCase)) { - return false; - } + if ((seen & AssemblyIdentityParts.ContentType) != 0) + { + return false; + } - parsedParts |= AssemblyIdentityParts.Retargetability; - } - else if (string.Equals(propertyName, "ContentType", StringComparison.OrdinalIgnoreCase)) - { - if ((seen & AssemblyIdentityParts.ContentType) != 0) - { - return false; - } + seen |= AssemblyIdentityParts.ContentType; - seen |= AssemblyIdentityParts.ContentType; + if (propertyValue == "*") + { + continue; + } - if (propertyValue == "*") - { - continue; - } + if (string.Equals(propertyValue, "WindowsRuntime", StringComparison.OrdinalIgnoreCase)) + { + contentType = AssemblyContentType.WindowsRuntime; + } + else + { + return false; + } - if (string.Equals(propertyValue, "WindowsRuntime", StringComparison.OrdinalIgnoreCase)) - { - contentType = AssemblyContentType.WindowsRuntime; + parsedParts |= AssemblyIdentityParts.ContentType; } else { - return false; + parsedParts |= AssemblyIdentityParts.Unknown; } - - parsedParts |= AssemblyIdentityParts.ContentType; } - else + + // incompatible values: + if (isRetargetable && contentType == AssemblyContentType.WindowsRuntime) { - parsedParts |= AssemblyIdentityParts.Unknown; + return false; } - } - // incompatible values: - if (isRetargetable && contentType == AssemblyContentType.WindowsRuntime) - { - return false; - } + bool hasPublicKey = !publicKey.IsDefault; + bool hasPublicKeyToken = !publicKeyToken.IsDefault; - bool hasPublicKey = !publicKey.IsDefault; - bool hasPublicKeyToken = !publicKeyToken.IsDefault; + identity = new AssemblyIdentity(simpleName, version, culture, hasPublicKey ? publicKey : publicKeyToken, hasPublicKey, isRetargetable, contentType); - identity = new AssemblyIdentity(simpleName, version, culture, hasPublicKey ? publicKey : publicKeyToken, hasPublicKey, isRetargetable, contentType); + if (hasPublicKey && hasPublicKeyToken && !identity.PublicKeyToken.SequenceEqual(publicKeyToken)) + { + identity = null; + return false; + } - if (hasPublicKey && hasPublicKeyToken && !identity.PublicKeyToken.SequenceEqual(publicKeyToken)) - { - identity = null; - return false; + parts = parsedParts; + return true; } - - parts = parsedParts; - return true; } private static bool TryParseNameToken(string displayName, ref int position, [NotNullWhen(true)] out string? value) From b0ffadd187e9b827e3b5ea3244f92539ebd4554c Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 11 Apr 2023 17:12:38 -0700 Subject: [PATCH 21/69] Pool lists used when reusing assembly symbols (#67744) --- .../CommonReferenceManager.Binding.cs | 342 +++++++++--------- 1 file changed, 178 insertions(+), 164 deletions(-) diff --git a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs index 32c53332f4a3e..3597cbb45f25e 100644 --- a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs +++ b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs @@ -704,236 +704,250 @@ private bool ReuseAssemblySymbolsWithNoPiaLocalTypes(BoundInputAssembly[] boundI return false; } + private static readonly ObjectPool> s_candidatesToExaminePool = new ObjectPool>(() => new Queue()); + private static readonly ObjectPool> s_candidateReferencedSymbolsPool = new ObjectPool>(() => new List(capacity: 1024)); + private void ReuseAssemblySymbols(BoundInputAssembly[] boundInputs, TAssemblySymbol[] candidateInputAssemblySymbols, ImmutableArray assemblies, int corLibraryIndex) { // Queue of references we need to examine for consistency - Queue candidatesToExamine = new Queue(); - int totalAssemblies = assemblies.Length; + Queue candidatesToExamine = s_candidatesToExaminePool.Allocate(); // A reusable buffer to contain the AssemblySymbols a candidate symbol refers to // ⚠ PERF: https://github.com/dotnet/roslyn/issues/47471 - List candidateReferencedSymbols = new List(1024); - - for (int i = 1; i < totalAssemblies; i++) + List candidateReferencedSymbols = s_candidateReferencedSymbolsPool.Allocate(); + try { - // We could have a match already - if (boundInputs[i].AssemblySymbol != null || assemblies[i].ContainsNoPiaLocalTypes) - { - continue; - } + int totalAssemblies = assemblies.Length; - foreach (TAssemblySymbol candidateAssembly in assemblies[i].AvailableSymbols) + for (int i = 1; i < totalAssemblies; i++) { - bool match = true; - - // We should examine this candidate, all its references that are supposed to - // match one of the given assemblies and do the same for their references, etc. - // The whole set of symbols we get at the end should be consistent with the set - // of assemblies we are given. The whole set of symbols should be accepted or rejected. - - // The set of symbols is accumulated in candidateInputAssemblySymbols. It is merged into - // boundInputs after consistency is confirmed. - Array.Clear(candidateInputAssemblySymbols, 0, candidateInputAssemblySymbols.Length); - - // Symbols and index of the corresponding assembly to match against are accumulated in the - // candidatesToExamine queue. They are examined one by one. - candidatesToExamine.Clear(); - - // This is a queue of symbols that we are picking up as a result of using - // symbols from candidateAssembly - candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(i, candidateAssembly)); + // We could have a match already + if (boundInputs[i].AssemblySymbol != null || assemblies[i].ContainsNoPiaLocalTypes) + { + continue; + } - while (match && candidatesToExamine.Count > 0) + foreach (TAssemblySymbol candidateAssembly in assemblies[i].AvailableSymbols) { - AssemblyReferenceCandidate candidate = candidatesToExamine.Dequeue(); + bool match = true; - Debug.Assert(candidate.DefinitionIndex >= 0); - Debug.Assert(candidate.AssemblySymbol is object); + // We should examine this candidate, all its references that are supposed to + // match one of the given assemblies and do the same for their references, etc. + // The whole set of symbols we get at the end should be consistent with the set + // of assemblies we are given. The whole set of symbols should be accepted or rejected. - int candidateIndex = candidate.DefinitionIndex; + // The set of symbols is accumulated in candidateInputAssemblySymbols. It is merged into + // boundInputs after consistency is confirmed. + Array.Clear(candidateInputAssemblySymbols, 0, candidateInputAssemblySymbols.Length); - // Have we already chosen symbols for the corresponding assembly? - Debug.Assert(boundInputs[candidateIndex].AssemblySymbol == null || - candidateInputAssemblySymbols[candidateIndex] == null); + // Symbols and index of the corresponding assembly to match against are accumulated in the + // candidatesToExamine queue. They are examined one by one. + candidatesToExamine.Clear(); - TAssemblySymbol? inputAssembly = boundInputs[candidateIndex].AssemblySymbol; - if (inputAssembly == null) - { - inputAssembly = candidateInputAssemblySymbols[candidateIndex]; - } + // This is a queue of symbols that we are picking up as a result of using + // symbols from candidateAssembly + candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(i, candidateAssembly)); - if (inputAssembly != null) + while (match && candidatesToExamine.Count > 0) { - if (Object.ReferenceEquals(inputAssembly, candidate.AssemblySymbol)) - { - // We already checked this AssemblySymbol, no reason to check it again - continue; // Proceed with the next assembly in candidatesToExamine queue. - } + AssemblyReferenceCandidate candidate = candidatesToExamine.Dequeue(); - // We are using different AssemblySymbol for this assembly - match = false; - break; // Stop processing items from candidatesToExamine queue. - } + Debug.Assert(candidate.DefinitionIndex >= 0); + Debug.Assert(candidate.AssemblySymbol is object); - // Candidate should be referenced the same way (/r or /l) by the compilation, - // which originated the symbols. We need this restriction in order to prevent - // non-interface generic types closed over NoPia local types from crossing - // assembly boundaries. - if (IsLinked(candidate.AssemblySymbol) != assemblies[candidateIndex].IsLinked) - { - match = false; - break; // Stop processing items from candidatesToExamine queue. - } - - // Add symbols to the set at corresponding index - Debug.Assert(candidateInputAssemblySymbols[candidateIndex] == null); - candidateInputAssemblySymbols[candidateIndex] = candidate.AssemblySymbol; - - // Now process references of the candidate. - - // how we bound the candidate references for this compilation: - var candidateReferenceBinding = boundInputs[candidateIndex].ReferenceBinding; + int candidateIndex = candidate.DefinitionIndex; - // get the AssemblySymbols the candidate symbol refers to into candidateReferencedSymbols - candidateReferencedSymbols.Clear(); - GetActualBoundReferencesUsedBy(candidate.AssemblySymbol, candidateReferencedSymbols); + // Have we already chosen symbols for the corresponding assembly? + Debug.Assert(boundInputs[candidateIndex].AssemblySymbol == null || + candidateInputAssemblySymbols[candidateIndex] == null); - Debug.Assert(candidateReferenceBinding is object); - Debug.Assert(candidateReferenceBinding.Length == candidateReferencedSymbols.Count); - int referencesCount = candidateReferencedSymbols.Count; + TAssemblySymbol? inputAssembly = boundInputs[candidateIndex].AssemblySymbol; + if (inputAssembly == null) + { + inputAssembly = candidateInputAssemblySymbols[candidateIndex]; + } - for (int k = 0; k < referencesCount; k++) - { - // All candidate's references that were /l-ed by the compilation, - // which originated the symbols, must be /l-ed by this compilation and - // other references must be either /r-ed or not referenced. - // We need this restriction in order to prevent non-interface generic types - // closed over NoPia local types from crossing assembly boundaries. - - // if target reference isn't resolved against given assemblies, - // we cannot accept a candidate that has the reference resolved. - if (!candidateReferenceBinding[k].IsBound) + if (inputAssembly != null) { - if (candidateReferencedSymbols[k] != null) + if (Object.ReferenceEquals(inputAssembly, candidate.AssemblySymbol)) { - // can't use symbols - - // If we decide do go back to accepting references like this, - // we should still not do this if the reference is a /l-ed assembly. - match = false; - break; // Stop processing references. + // We already checked this AssemblySymbol, no reason to check it again + continue; // Proceed with the next assembly in candidatesToExamine queue. } - continue; // Proceed with the next reference. - } - - // We resolved the reference, candidate must have that reference resolved too. - var currentCandidateReferencedSymbol = candidateReferencedSymbols[k]; - if (currentCandidateReferencedSymbol == null) - { - // can't use symbols + // We are using different AssemblySymbol for this assembly match = false; - break; // Stop processing references. + break; // Stop processing items from candidatesToExamine queue. } - int definitionIndex = candidateReferenceBinding[k].DefinitionIndex; - if (definitionIndex == 0) + // Candidate should be referenced the same way (/r or /l) by the compilation, + // which originated the symbols. We need this restriction in order to prevent + // non-interface generic types closed over NoPia local types from crossing + // assembly boundaries. + if (IsLinked(candidate.AssemblySymbol) != assemblies[candidateIndex].IsLinked) { - // We can't reuse any assembly that refers to the assembly being built. match = false; - break; + break; // Stop processing items from candidatesToExamine queue. } - // Make sure symbols represent the same assembly/binary - if (!assemblies[definitionIndex].IsMatchingAssembly(currentCandidateReferencedSymbol)) - { - // Mismatch between versions? - match = false; - break; // Stop processing references. - } + // Add symbols to the set at corresponding index + Debug.Assert(candidateInputAssemblySymbols[candidateIndex] == null); + candidateInputAssemblySymbols[candidateIndex] = candidate.AssemblySymbol; - if (assemblies[definitionIndex].ContainsNoPiaLocalTypes) - { - // We already know that we cannot reuse any existing symbols for - // this assembly - match = false; - break; // Stop processing references. - } + // Now process references of the candidate. - if (IsLinked(currentCandidateReferencedSymbol) != assemblies[definitionIndex].IsLinked) - { - // Mismatch between reference kind. - match = false; - break; // Stop processing references. - } + // how we bound the candidate references for this compilation: + var candidateReferenceBinding = boundInputs[candidateIndex].ReferenceBinding; - // Add this reference to the queue so that we consider it as a candidate too - candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(definitionIndex, currentCandidateReferencedSymbol)); - } + // get the AssemblySymbols the candidate symbol refers to into candidateReferencedSymbols + candidateReferencedSymbols.Clear(); + GetActualBoundReferencesUsedBy(candidate.AssemblySymbol, candidateReferencedSymbols); - // Check that the COR library used by the candidate assembly symbol is the same as the one use by this compilation. - if (match) - { - TAssemblySymbol? candidateCorLibrary = GetCorLibrary(candidate.AssemblySymbol); + Debug.Assert(candidateReferenceBinding is object); + Debug.Assert(candidateReferenceBinding.Length == candidateReferencedSymbols.Count); + int referencesCount = candidateReferencedSymbols.Count; - if (candidateCorLibrary == null) + for (int k = 0; k < referencesCount; k++) { - // If the candidate didn't have a COR library, that is fine as long as we don't have one either. - if (corLibraryIndex >= 0) + // All candidate's references that were /l-ed by the compilation, + // which originated the symbols, must be /l-ed by this compilation and + // other references must be either /r-ed or not referenced. + // We need this restriction in order to prevent non-interface generic types + // closed over NoPia local types from crossing assembly boundaries. + + // if target reference isn't resolved against given assemblies, + // we cannot accept a candidate that has the reference resolved. + if (!candidateReferenceBinding[k].IsBound) + { + if (candidateReferencedSymbols[k] != null) + { + // can't use symbols + + // If we decide do go back to accepting references like this, + // we should still not do this if the reference is a /l-ed assembly. + match = false; + break; // Stop processing references. + } + + continue; // Proceed with the next reference. + } + + // We resolved the reference, candidate must have that reference resolved too. + var currentCandidateReferencedSymbol = candidateReferencedSymbols[k]; + if (currentCandidateReferencedSymbol == null) { + // can't use symbols match = false; break; // Stop processing references. } - } - else - { - // We can't be compiling corlib and have a corlib reference at the same time: - Debug.Assert(corLibraryIndex != 0); - Debug.Assert(ReferenceEquals(candidateCorLibrary, GetCorLibrary(candidateCorLibrary))); + int definitionIndex = candidateReferenceBinding[k].DefinitionIndex; + if (definitionIndex == 0) + { + // We can't reuse any assembly that refers to the assembly being built. + match = false; + break; + } - // Candidate has COR library, we should have one too. - if (corLibraryIndex < 0) + // Make sure symbols represent the same assembly/binary + if (!assemblies[definitionIndex].IsMatchingAssembly(currentCandidateReferencedSymbol)) { + // Mismatch between versions? match = false; break; // Stop processing references. } - // Make sure candidate COR library represent the same assembly/binary - if (!assemblies[corLibraryIndex].IsMatchingAssembly(candidateCorLibrary)) + if (assemblies[definitionIndex].ContainsNoPiaLocalTypes) { - // Mismatch between versions? + // We already know that we cannot reuse any existing symbols for + // this assembly + match = false; + break; // Stop processing references. + } + + if (IsLinked(currentCandidateReferencedSymbol) != assemblies[definitionIndex].IsLinked) + { + // Mismatch between reference kind. match = false; break; // Stop processing references. } - Debug.Assert(!assemblies[corLibraryIndex].ContainsNoPiaLocalTypes); - Debug.Assert(!assemblies[corLibraryIndex].IsLinked); - Debug.Assert(!IsLinked(candidateCorLibrary)); + // Add this reference to the queue so that we consider it as a candidate too + candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(definitionIndex, currentCandidateReferencedSymbol)); + } + + // Check that the COR library used by the candidate assembly symbol is the same as the one use by this compilation. + if (match) + { + TAssemblySymbol? candidateCorLibrary = GetCorLibrary(candidate.AssemblySymbol); - // Add the candidate COR library to the queue so that we consider it as a candidate. - candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(corLibraryIndex, candidateCorLibrary)); + if (candidateCorLibrary == null) + { + // If the candidate didn't have a COR library, that is fine as long as we don't have one either. + if (corLibraryIndex >= 0) + { + match = false; + break; // Stop processing references. + } + } + else + { + // We can't be compiling corlib and have a corlib reference at the same time: + Debug.Assert(corLibraryIndex != 0); + + Debug.Assert(ReferenceEquals(candidateCorLibrary, GetCorLibrary(candidateCorLibrary))); + + // Candidate has COR library, we should have one too. + if (corLibraryIndex < 0) + { + match = false; + break; // Stop processing references. + } + + // Make sure candidate COR library represent the same assembly/binary + if (!assemblies[corLibraryIndex].IsMatchingAssembly(candidateCorLibrary)) + { + // Mismatch between versions? + match = false; + break; // Stop processing references. + } + + Debug.Assert(!assemblies[corLibraryIndex].ContainsNoPiaLocalTypes); + Debug.Assert(!assemblies[corLibraryIndex].IsLinked); + Debug.Assert(!IsLinked(candidateCorLibrary)); + + // Add the candidate COR library to the queue so that we consider it as a candidate. + candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(corLibraryIndex, candidateCorLibrary)); + } } } - } - if (match) - { - // Merge the set of symbols into result - for (int k = 0; k < totalAssemblies; k++) + if (match) { - if (candidateInputAssemblySymbols[k] != null) + // Merge the set of symbols into result + for (int k = 0; k < totalAssemblies; k++) { - Debug.Assert(boundInputs[k].AssemblySymbol == null); - boundInputs[k].AssemblySymbol = candidateInputAssemblySymbols[k]; + if (candidateInputAssemblySymbols[k] != null) + { + Debug.Assert(boundInputs[k].AssemblySymbol == null); + boundInputs[k].AssemblySymbol = candidateInputAssemblySymbols[k]; + } } - } - // No reason to examine other symbols for this assembly - break; // Stop processing assemblies[i].AvailableSymbols + // No reason to examine other symbols for this assembly + break; // Stop processing assemblies[i].AvailableSymbols + } } } } + finally + { + candidatesToExamine.Clear(); + candidateReferencedSymbols.Clear(); + + s_candidatesToExaminePool.Free(candidatesToExamine); + s_candidateReferencedSymbolsPool.Free(candidateReferencedSymbols); + } } private static bool CheckCircularReference(IReadOnlyList referenceBindings) From 3b23723934d3c7d0b3c1a0165def4a7f0e6e3a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Tue, 11 Apr 2023 19:13:02 -0700 Subject: [PATCH 22/69] Set current culture when Interactive Window is initialized (#67677) --- .../Core/InteractiveHost.LazyRemoteService.cs | 4 ++-- .../Interactive/Core/InteractiveHost.Service.cs | 12 +++--------- .../HostProcess/InteractiveHostEntryPoint.cs | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs index b1bde4f836a6b..67cebd97ee977 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.LazyRemoteService.cs @@ -143,7 +143,7 @@ private async Task TryStartAndInitializeProcessAsync(C { StartInfo = new ProcessStartInfo(hostPath) { - Arguments = pipeName + " " + currentProcessId, + Arguments = $"{pipeName} {currentProcessId} \"{culture.Name}\"", WorkingDirectory = Host._initialWorkingDirectory, CreateNoWindow = true, UseShellExecute = false, @@ -211,7 +211,7 @@ void ProcessExitedBeforeEstablishingConnection(object sender, EventArgs e) platformInfo = (await jsonRpc.InvokeWithCancellationAsync( nameof(Service.InitializeAsync), - new object[] { Host._replServiceProviderType.AssemblyQualifiedName, culture.Name }, + new object[] { Host._replServiceProviderType.AssemblyQualifiedName }, cancellationToken).ConfigureAwait(false)).Deserialize(); } catch (Exception e) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs index 15da788d3678e..ddadfb273b2ec 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.Service.cs @@ -160,13 +160,13 @@ public void Dispose() _serviceState = null; } - public Task InitializeAsync(string replServiceProviderTypeName, string cultureName) + public Task InitializeAsync(string replServiceProviderTypeName) { // TODO (tomat): we should share the copied files with the host var metadataFileProvider = new MetadataShadowCopyProvider( Path.Combine(Path.GetTempPath(), "InteractiveHostShadow"), noShadowCopyDirectories: s_systemNoShadowCopyDirectories, - documentationCommentsCulture: new CultureInfo(cultureName)); + documentationCommentsCulture: CultureInfo.CurrentUICulture); var assemblyLoader = new InteractiveAssemblyLoader(metadataFileProvider); var replServiceProviderType = Type.GetType(replServiceProviderTypeName); @@ -213,16 +213,10 @@ public void EmulateClientExit() s_clientExited.Set(); } - internal static Task RunServerAsync(string[] args, Func, object> invokeOnMainThread) - { - Contract.ThrowIfFalse(args.Length == 2, "Expecting arguments: "); - return RunServerAsync(args[0], int.Parse(args[1], CultureInfo.InvariantCulture), invokeOnMainThread); - } - /// /// Implements remote server. /// - private static async Task RunServerAsync(string pipeName, int clientProcessId, Func, object> invokeOnMainThread) + public static async Task RunServerAsync(string pipeName, int clientProcessId, Func, object> invokeOnMainThread) { if (!AttachToClientProcess(clientProcessId)) { diff --git a/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs b/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs index 23821c81066a6..c94695ff1a9be 100644 --- a/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs +++ b/src/Interactive/HostProcess/InteractiveHostEntryPoint.cs @@ -3,11 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.CodeAnalysis.ErrorReporting; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Interactive { @@ -20,6 +22,18 @@ private static async Task Main(string[] args) // Disables Windows Error Reporting for the process, so that the process fails fast. SetErrorMode(GetErrorMode() | ErrorMode.SEM_FAILCRITICALERRORS | ErrorMode.SEM_NOOPENFILEERRORBOX | ErrorMode.SEM_NOGPFAULTERRORBOX); + Contract.ThrowIfFalse(args.Length == 3, "Expecting arguments: "); + + var pipeName = args[0]; + var clientProcessId = int.Parse(args[1], CultureInfo.InvariantCulture); + var cultureName = args[2]; + var culture = new CultureInfo(cultureName); + + CultureInfo.CurrentCulture = culture; + CultureInfo.CurrentUICulture = culture; + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; + Control? control = null; using (var resetEvent = new ManualResetEventSlim(false)) { @@ -41,7 +55,7 @@ private static async Task Main(string[] args) try { - await InteractiveHost.Service.RunServerAsync(args, invokeOnMainThread).ConfigureAwait(false); + await InteractiveHost.Service.RunServerAsync(pipeName, clientProcessId, invokeOnMainThread).ConfigureAwait(false); return 0; } catch (Exception e) From 26d625ff5421473c84589731325fe0c4ec85a74a Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 11 Apr 2023 23:31:23 -0700 Subject: [PATCH 23/69] Address feedback --- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 81ba6d4790f44..4d6971b70a2c8 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -234,21 +234,21 @@ public async Task TryGetAsync(ArrayBuilder list, Cancellat : _diagnosticKind == DiagnosticKind.AnalyzerSemantic; } + includeSyntax = includeSyntax && analyzer.SupportAnalysisKind(AnalysisKind.Syntax); + includeSemantic = includeSemantic && analyzer.SupportAnalysisKind(AnalysisKind.Semantic) && _document is Document; + if (includeSyntax || includeSemantic) { var state = stateSet.GetOrCreateActiveFileState(_document.Id); - if (includeSyntax && - analyzer.SupportAnalysisKind(AnalysisKind.Syntax)) + if (includeSyntax) { var existingData = state.GetAnalysisData(AnalysisKind.Syntax); if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Syntax, existingData, list, cancellationToken).ConfigureAwait(false)) syntaxAnalyzers.Add(new AnalyzerWithState(stateSet.Analyzer, state, existingData)); } - if (includeSemantic && - analyzer.SupportAnalysisKind(AnalysisKind.Semantic) && - _document is Document) + if (includeSemantic) { var existingData = state.GetAnalysisData(AnalysisKind.Semantic); if (!await TryAddCachedDocumentDiagnosticsAsync(stateSet.Analyzer, AnalysisKind.Semantic, existingData, list, cancellationToken).ConfigureAwait(false)) @@ -385,7 +385,6 @@ private async Task ComputeDocumentDiagnosticsAsync( Debug.Assert(!incrementalAnalysis || analyzersWithState.All(analyzerWithState => analyzerWithState.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis())); using var _ = ArrayBuilder.GetInstance(analyzersWithState.Length, out var filteredAnalyzersWithStateBuilder); - var anyDeprioritized = false; foreach (var analyzerWithState in analyzersWithState) { Debug.Assert(_priorityProvider.MatchesPriority(analyzerWithState.Analyzer)); @@ -395,19 +394,17 @@ private async Task ComputeDocumentDiagnosticsAsync( // We will subsequently execute this analyzer in the lower priority bucket. if (await TryDeprioritizeAnalyzerAsync(analyzerWithState.Analyzer, analyzerWithState.ExistingData).ConfigureAwait(false)) { - anyDeprioritized = true; continue; } filteredAnalyzersWithStateBuilder.Add(analyzerWithState); } - if (anyDeprioritized) - analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); - - if (analyzersWithState.IsEmpty) + if (filteredAnalyzersWithStateBuilder.Count == 0) return; + analyzersWithState = filteredAnalyzersWithStateBuilder.ToImmutable(); + var analyzers = analyzersWithState.SelectAsArray(stateSet => stateSet.Analyzer); var analysisScope = new DocumentAnalysisScope(_document, span, analyzers, kind); var executor = new DocumentAnalysisExecutor(analysisScope, _compilationWithAnalyzers, _owner._diagnosticAnalyzerRunner, _isExplicit, _logPerformanceInfo); From de7a6cbed1625a1bc73641b3987348b3f69b160f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 10:29:37 -0700 Subject: [PATCH 24/69] Pool node collections in formatter --- src/Dependencies/PooledObjects/ObjectPool`1.cs | 12 ++++++++---- .../Formatting/Engine/AbstractFormatEngine.cs | 16 +++++++++++----- .../Compiler/Core/ObjectPools/Extensions.cs | 4 ++-- .../Compiler/Core/ObjectPools/PooledObject.cs | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Dependencies/PooledObjects/ObjectPool`1.cs b/src/Dependencies/PooledObjects/ObjectPool`1.cs index d9dca9dfb14f8..043a52b9ccc53 100644 --- a/src/Dependencies/PooledObjects/ObjectPool`1.cs +++ b/src/Dependencies/PooledObjects/ObjectPool`1.cs @@ -63,6 +63,8 @@ private struct Element // than "new T()". private readonly Factory _factory; + public readonly bool TrimOnFree; + #if DETECT_LEAKS private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable(); @@ -104,15 +106,17 @@ private string GetTrace() } #endif - internal ObjectPool(Factory factory) - : this(factory, Environment.ProcessorCount * 2) - { } + internal ObjectPool(Factory factory, bool trimOnFree = true) + : this(factory, Environment.ProcessorCount * 2, trimOnFree) + { + } - internal ObjectPool(Factory factory, int size) + internal ObjectPool(Factory factory, int size, bool trimOnFree = true) { Debug.Assert(size >= 1); _factory = factory; _items = new Element[size - 1]; + TrimOnFree = trimOnFree; } internal ObjectPool(Func, T> factory, int size) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 96256475649f9..ad284990ee228 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -28,6 +29,11 @@ namespace Microsoft.CodeAnalysis.Formatting // that would create too big graph. key for this approach is how to reduce size of graph. internal abstract partial class AbstractFormatEngine { + // Intentionally do not trim the capacities of these collections down. We will repeatedly try to format large + // files as we edit them and this will produce a lot of garbage as we free the internal array backing the list + // over and over again. + private static readonly ObjectPool> s_nodeIteratorPool = new(() => new(), trimOnFree: false); + private readonly ChainedFormattingRules _formattingRules; private readonly SyntaxNode _commonRoot; @@ -126,19 +132,19 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella cancellationToken.ThrowIfCancellationRequested(); // iterating tree is very expensive. do it once and cache it to list - SegmentedList nodeIterator; + using var pooledIterator = s_nodeIteratorPool.GetPooledObject(); + + var nodeIterator = pooledIterator.Object; using (Logger.LogBlock(FunctionId.Formatting_IterateNodes, cancellationToken)) { const int magicLengthToNodesRatio = 5; - var result = new SegmentedList(Math.Max(this.SpanToFormat.Length / magicLengthToNodesRatio, 4)); + nodeIterator.Capacity = Math.Max(nodeIterator.Capacity, Math.Max(this.SpanToFormat.Length / magicLengthToNodesRatio, 4)); foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { cancellationToken.ThrowIfCancellationRequested(); - result.Add(node); + nodeIterator.Add(node); } - - nodeIterator = result; } // iterate through each operation using index to not create any unnecessary object diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs index a009bbcb30b39..6fa2595554ba6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs @@ -285,7 +285,7 @@ public static void ClearAndFree(this ObjectPool> pool, List list) pool.Free(list); } - public static void ClearAndFree(this ObjectPool> pool, SegmentedList list) + public static void ClearAndFree(this ObjectPool> pool, SegmentedList list, bool trim = true) { if (list == null) { @@ -294,7 +294,7 @@ public static void ClearAndFree(this ObjectPool> pool, Segme list.Clear(); - if (list.Capacity > Threshold) + if (trim && list.Capacity > Threshold) { list.Capacity = Threshold; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs index 98ad09b8e3c8b..67bcbf55d7bd6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs @@ -140,7 +140,7 @@ private static SegmentedList Allocator(ObjectPool pool.AllocateAndClear(); private static void Releaser(ObjectPool> pool, SegmentedList obj) - => pool.ClearAndFree(obj); + => pool.ClearAndFree(obj, pool.TrimOnFree); #endregion } } From 5eee91922effa1f422adda4abc3cae75c20bd853 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 11:24:31 -0700 Subject: [PATCH 25/69] Pool more formatting collections --- .../Formatting/Engine/AbstractFormatEngine.cs | 27 ++++++-------- .../Formatting/Engine/TokenStream.Iterator.cs | 36 +++---------------- .../Core/Formatting/Engine/TokenStream.cs | 9 ++--- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index ad284990ee228..70cee7b3e27ba 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -33,6 +33,7 @@ internal abstract partial class AbstractFormatEngine // files as we edit them and this will produce a lot of garbage as we free the internal array backing the list // over and over again. private static readonly ObjectPool> s_nodeIteratorPool = new(() => new(), trimOnFree: false); + private static readonly ObjectPool> s_tokenPairListPool = new(() => new(), trimOnFree: false); private readonly ChainedFormattingRules _formattingRules; @@ -94,7 +95,8 @@ public AbstractFormattingResult Format(CancellationToken cancellationToken) var nodeOperations = CreateNodeOperations(cancellationToken); var tokenStream = new TokenStream(this.TreeData, Options, this.SpanToFormat, CreateTriviaFactory()); - var tokenOperation = CreateTokenOperation(tokenStream, cancellationToken); + using var tokenOperations = s_tokenPairListPool.GetPooledObject(); + AddTokenOperations(tokenStream, tokenOperations.Object, cancellationToken); // initialize context var context = CreateFormattingContext(tokenStream, cancellationToken); @@ -107,8 +109,7 @@ public AbstractFormattingResult Format(CancellationToken cancellationToken) ApplyBeginningOfTreeTriviaOperation(context, cancellationToken); - ApplyTokenOperations(context, nodeOperations, - tokenOperation, cancellationToken); + ApplyTokenOperations(context, nodeOperations, tokenOperations.Object, cancellationToken); ApplyTriviaOperations(context, cancellationToken); @@ -202,17 +203,15 @@ private static List AddOperations(SegmentedList nodes, Action< return operations; } - private SegmentedArray CreateTokenOperation( + private void AddTokenOperations( TokenStream tokenStream, + SegmentedList list, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (Logger.LogBlock(FunctionId.Formatting_CollectTokenOperation, cancellationToken)) { - // pre-allocate list once. this is cheaper than re-adjusting list as items are added. - var list = new SegmentedArray(tokenStream.TokenCount - 1); - foreach (var (index, currentToken, nextToken) in tokenStream.TokenIterator) { cancellationToken.ThrowIfCancellationRequested(); @@ -220,17 +219,15 @@ private SegmentedArray CreateTokenOperation( var spaceOperation = _formattingRules.GetAdjustSpacesOperation(currentToken, nextToken); var lineOperation = _formattingRules.GetAdjustNewLinesOperation(currentToken, nextToken); - list[index] = new TokenPairWithOperations(tokenStream, index, spaceOperation, lineOperation); + list.Add(new TokenPairWithOperations(tokenStream, index, spaceOperation, lineOperation)); } - - return list; } } private void ApplyTokenOperations( FormattingContext context, NodeOperations nodeOperations, - SegmentedArray tokenOperations, + SegmentedList tokenOperations, CancellationToken cancellationToken) { var applier = new OperationApplier(context, _formattingRules); @@ -358,7 +355,7 @@ private static void ApplySpecialOperations( private static void ApplyAnchorOperations( FormattingContext context, - SegmentedArray tokenOperations, + SegmentedList tokenOperations, OperationApplier applier, CancellationToken cancellationToken) { @@ -370,9 +367,7 @@ private static void ApplyAnchorOperations( { cancellationToken.ThrowIfCancellationRequested(); if (!AnchorOperationCandidate(p)) - { continue; - } var pairIndex = p.PairIndex; applier.ApplyAnchorIndentation(pairIndex, previousChangesMap, cancellationToken); @@ -415,7 +410,7 @@ private static SyntaxToken FindCorrectBaseTokenOfRelativeIndentBlockOperation(In private static void ApplySpaceAndWrappingOperations( FormattingContext context, - SegmentedArray tokenOperations, + SegmentedList tokenOperations, OperationApplier applier, CancellationToken cancellationToken) { @@ -423,9 +418,7 @@ private static void ApplySpaceAndWrappingOperations( { // go through each token pairs and apply operations foreach (var operationPair in tokenOperations) - { ApplySpaceAndWrappingOperationsBody(context, operationPair, applier, cancellationToken); - } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.Iterator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.Iterator.cs index 79efdb4985940..fd8fc491ce50a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.Iterator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.Iterator.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using Microsoft.CodeAnalysis.Collections; namespace Microsoft.CodeAnalysis.Formatting @@ -12,20 +10,17 @@ internal partial class TokenStream { // gain of having hand written iterator seems about 50-100ms over auto generated one. // not sure whether it is worth it. but I already wrote it to test, so going to just keep it. - private class Iterator : IEnumerable<(int index, SyntaxToken currentToken, SyntaxToken nextToken)> + public readonly struct Iterator { private readonly SegmentedList _tokensIncludingZeroWidth; public Iterator(SegmentedList tokensIncludingZeroWidth) => _tokensIncludingZeroWidth = tokensIncludingZeroWidth; - public IEnumerator<(int index, SyntaxToken currentToken, SyntaxToken nextToken)> GetEnumerator() - => new Enumerator(_tokensIncludingZeroWidth); + public Enumerator GetEnumerator() + => new(_tokensIncludingZeroWidth); - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - => GetEnumerator(); - - private struct Enumerator : IEnumerator<(int index, SyntaxToken currentToken, SyntaxToken nextToken)> + public struct Enumerator { private readonly SegmentedList _tokensIncludingZeroWidth; private readonly int _maxCount; @@ -42,10 +37,6 @@ public Enumerator(SegmentedList tokensIncludingZeroWidth) _current = default; } - public readonly void Dispose() - { - } - public bool MoveNext() { if (_index < _maxCount) @@ -66,25 +57,6 @@ private bool MoveNextRare() } public readonly (int index, SyntaxToken currentToken, SyntaxToken nextToken) Current => _current; - - readonly object System.Collections.IEnumerator.Current - { - get - { - if (_index == 0 || _index == _maxCount + 1) - { - throw new InvalidOperationException(); - } - - return Current; - } - } - - void System.Collections.IEnumerator.Reset() - { - _index = 0; - _current = new ValueTuple(); - } } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs index adb3cef214c0c..f1062caa03050 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs @@ -550,13 +550,8 @@ private int GetTokenIndexInStream(SyntaxToken token) return -1; } - public IEnumerable<(int index, SyntaxToken currentToken, SyntaxToken nextToken)> TokenIterator - { - get - { - return new Iterator(_tokens); - } - } + public Iterator TokenIterator + => new(_tokens); private sealed class TokenOrderComparer : IComparer { From 37a9b38ef30ace6d2872b99b21ff6874e072ba4e Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 11:25:23 -0700 Subject: [PATCH 26/69] Check client capability --- .../CodeActions/CodeActionResolveHandler.cs | 111 ++++++++---------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 04f6b05feae98..a94938f1cd4f4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -155,6 +156,19 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) return codeAction; } } + + if (projectChange.GetChangedDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution)) + || projectChange.GetChangedAdditionalDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution) + || projectChange.GetChangedAnalyzerConfigDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution)))) + { + if (context.GetRequiredClientCapabilities() is not { Workspace.WorkspaceEdit.ResourceOperations: { } resourceOperations } + || !resourceOperations.Contains(ResourceOperationKind.Rename)) + { + // Rename documents is not supported by this workspace + codeAction.Edit = new LSP.WorkspaceEdit { DocumentChanges = Array.Empty() }; + return codeAction; + } + } } #if false @@ -264,8 +278,16 @@ async Task AddTextDocumentAdditionsAsync( var newTextDoc = getNewDocument(docId); Contract.ThrowIfNull(newTextDoc); + // Create the document as empty + textDocumentEdits.Add(new CreateFile() { Uri = newTextDoc.GetURI() }); + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - CopyDocumentToNewLocation(newTextDoc.GetURI(), newText, textDocumentEdits); + + // And then give it content + var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; + var edit = new TextEdit { Range = emptyDocumentRange, NewText = newText.ToString() }; + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); } } @@ -283,83 +305,50 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); // If the document has text change. - if (newTextDoc.HasTextChanged(oldTextDoc, ignoreUnchangeableDocument: false)) - { - var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - IEnumerable textChanges; + var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' - // method instead, we would get a change that spans the entire document, which we ideally want to avoid. - if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) - { - Contract.ThrowIfNull(textDiffService); - textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); - } - else - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText); - } + IEnumerable textChanges; - var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' + // method instead, we would get a change that spans the entire document, which we ideally want to avoid. + if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) + { + Contract.ThrowIfNull(textDiffService); + textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); } - - // If the document has name/folder/filePath change - if (newTextDoc.HasInfoChanged(oldTextDoc)) + else { - var oldDocumentAttribute = oldTextDoc.State.Attributes; - var newDocumentAttribute = newTextDoc.State.Attributes; - - // Rename - if (oldDocumentAttribute.Name != newDocumentAttribute.Name) - { - textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); - } + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + textChanges = newText.GetTextChanges(oldText); + } - // Move FilePath - if (oldDocumentAttribute.FilePath != newDocumentAttribute.FilePath) - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - CutDocumentToNewLocation(newTextDoc.GetURI(), oldTextDoc.GetURI(), newText, textDocumentEdits); - } + var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); - // folder change - if (!oldDocumentAttribute.Folders.SequenceEqual(newDocumentAttribute.Folders)) - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - CutDocumentToNewLocation(newTextDoc.GetUriFromContainingFolders(), oldTextDoc.GetUriFromContainingFolders(), newText, textDocumentEdits); - } + // Rename + if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) + { + textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); } } } - static void CutDocumentToNewLocation( - Uri newLocation, - Uri oldLocation, - SourceText sourceText, - ArrayBuilder> textDocumentEdits) - { - CopyDocumentToNewLocation(newLocation, sourceText, textDocumentEdits); - textDocumentEdits.Add(new DeleteFile() { Uri = oldLocation }); - } - static void CopyDocumentToNewLocation( Uri newLocation, SourceText sourceText, ArrayBuilder> textDocumentEdits) { - // Create the document as empty - textDocumentEdits.Add(new CreateFile() { Uri = newLocation }); - - // And then give it content - var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; - var edit = new TextEdit { Range = emptyDocumentRange, NewText = sourceText.ToString() }; - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newLocation }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = new[] { edit } }); + } } + + private static bool HasDocumentNameChange(DocumentId documentId, Solution newSolution, Solution oldSolution) + { + var newDocument = newSolution.GetRequiredDocument(documentId); + var oldDocument = oldSolution.GetRequiredDocument(documentId); + return newDocument.State.Name != oldDocument.State.Name; + } } } From eba0d810f5e31d17b842924894ceb79e668ad944 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 11:27:47 -0700 Subject: [PATCH 27/69] Code clean up --- .../Protocol/Extensions/Extensions.cs | 11 ----------- .../Handler/CodeActions/CodeActionResolveHandler.cs | 13 ++----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index d6216e55e3571..7abd01219cfe5 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -42,17 +42,6 @@ public static Uri GetUriFromName(this TextDocument document) : ProtocolConversions.GetUriFromFilePath(path); } - public static Uri GetUriFromContainingFolders(this TextDocument document) - { - Contract.ThrowIfTrue(document.Folders.Count == 0); - Contract.ThrowIfNull(document.Name); - - var path = Path.Combine(document.Folders.Concat(document.Name).AsArray()); - return document is SourceGeneratedDocument - ? ProtocolConversions.GetUriFromPartialFilePath(path) - : ProtocolConversions.GetUriFromFilePath(path); - } - public static Uri? TryGetURI(this TextDocument document, RequestContext? context = null) => ProtocolConversions.TryGetUriFromFilePath(document.FilePath, context); diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index a94938f1cd4f4..725a8ed74ae05 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -279,10 +279,9 @@ async Task AddTextDocumentAdditionsAsync( Contract.ThrowIfNull(newTextDoc); // Create the document as empty - textDocumentEdits.Add(new CreateFile() { Uri = newTextDoc.GetURI() }); + textDocumentEdits.Add(new CreateFile { Uri = newTextDoc.GetURI() }); var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - // And then give it content var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; var edit = new TextEdit { Range = emptyDocumentRange, NewText = newText.ToString() }; @@ -304,8 +303,8 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); - // If the document has text change. + // If the document has text change. var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); IEnumerable textChanges; @@ -334,14 +333,6 @@ async Task AddTextDocumentEditsAsync( } } } - - static void CopyDocumentToNewLocation( - Uri newLocation, - SourceText sourceText, - ArrayBuilder> textDocumentEdits) - { - - } } private static bool HasDocumentNameChange(DocumentId documentId, Solution newSolution, Solution oldSolution) From d2aeb07071fec9cb899e31c244fcce0facf4a20b Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 11:37:10 -0700 Subject: [PATCH 28/69] Fix test --- .../CodeActions/CodeActionResolveHandler.cs | 7 +++++-- .../CodeActions/CodeActionResolveTests.cs | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 725a8ed74ae05..e0d6b9912770f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -323,8 +323,11 @@ async Task AddTextDocumentEditsAsync( } var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + if (edits.Length > 0) + { + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + } // Rename if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index f51e3ddd0ed88..8a0c418a95fe1 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -137,14 +137,26 @@ void M() } [WpfTheory, CombinatorialData] - public async Task Test(bool mutatingLspWorkspace) + public async Task TestRename(bool mutatingLspWorkspace) { var markUp = @" class {|caret:ABC|} { }"; - await using var testLspServer = await CreateTestLspServerAsync(markUp, mutatingLspWorkspace); + await using var testLspServer = await CreateTestLspServerAsync(markUp, mutatingLspWorkspace, new InitializationOptions + { + ClientCapabilities = new ClientCapabilities() + { + Workspace = new WorkspaceClientCapabilities + { + WorkspaceEdit = new WorkspaceEditSetting + { + ResourceOperations = new ResourceOperationKind[] { ResourceOperationKind.Rename } + } + } + } + }); var unresolvedCodeAction = CodeActionsTests.CreateCodeAction( title: string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), kind: CodeActionKind.Refactor, From ac93e6f858b6051dc35810be3234003820604cbe Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 13:34:14 -0700 Subject: [PATCH 29/69] Use a hashset to filter out unneeded changes --- .../CodeActions/CodeActionResolveHandler.cs | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index c7b52ccb984f7..86d2363a7e478 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -90,7 +90,8 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) var textDiffService = solution.Services.GetService(); - using var _ = ArrayBuilder>.GetInstance(out var textDocumentEdits); + using var _1 = ArrayBuilder>.GetInstance(out var textDocumentEdits); + using var _2 = PooledHashSet.GetInstance(out var modifiedDocumentIds); foreach (var option in operations) { @@ -290,26 +291,33 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); - var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + // For linked documents, only generated the document edit once. + if (modifiedDocumentIds.Add(docId)) + { + var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - IEnumerable textChanges; + IEnumerable textChanges; - // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' - // method instead, we would get a change that spans the entire document, which we ideally want to avoid. - if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) - { - Contract.ThrowIfNull(textDiffService); - textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); - } - else - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText); - } + // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' + // method instead, we would get a change that spans the entire document, which we ideally want to avoid. + if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) + { + Contract.ThrowIfNull(textDiffService); + textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); + } + else + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + textChanges = newText.GetTextChanges(oldText); + } - var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + + var linkedDocuments = solution.GetRelatedDocumentIds(docId); + modifiedDocumentIds.AddRange(linkedDocuments); + } } } } From f0f0a97928c545fa14b9797f97b98f499638f6b0 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 12 Apr 2023 14:07:22 -0700 Subject: [PATCH 30/69] Reduce Location instance allocations. (#67772) * Do not store location instances in enum symbols, store spans instead * Do not store location instances in local symbols * Reduce allocations in getting locations for a LocalFunctionSymbol * Add using back in --- .../Binder/ExpressionVariableFinder.cs | 6 +-- .../Source/GlobalExpressionVariable.cs | 15 ++++---- .../Symbols/Source/LocalFunctionSymbol.cs | 2 + .../Source/SourceEnumConstantSymbol.cs | 2 +- .../Symbols/Source/SourceFieldSymbol.cs | 38 ++++++------------- .../Symbols/Source/SourceLocalSymbol.cs | 19 ++++------ .../Symbols/Source/SourceMemberFieldSymbol.cs | 6 +-- 7 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs index ecb5ea0a48ea8..3b02eec015aab 100644 --- a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs @@ -649,7 +649,7 @@ protected override Symbol MakePatternVariable(TypeSyntax type, SingleVariableDes { return designation == null ? null : GlobalExpressionVariable.Create( _containingType, _modifiers, type, - designation.Identifier.ValueText, designation, designation.GetLocation(), + designation.Identifier.ValueText, designation, designation.Span, _containingFieldOpt, nodeToBind); } @@ -657,7 +657,7 @@ protected override Symbol MakeDeclarationExpressionVariable(DeclarationExpressio { return GlobalExpressionVariable.Create( _containingType, _modifiers, node.Type, - designation.Identifier.ValueText, designation, designation.Identifier.GetLocation(), + designation.Identifier.ValueText, designation, designation.Identifier.Span, _containingFieldOpt, nodeToBind); } @@ -672,7 +672,7 @@ protected override Symbol MakeDeconstructionVariable( typeSyntax: closestTypeSyntax, name: designation.Identifier.ValueText, syntax: designation, - location: designation.Location, + locationSpan: designation.Span, containingFieldOpt: null, nodeToBind: deconstruction); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs b/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs index 9898f52773941..64b9a72c48268 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -30,8 +31,8 @@ internal GlobalExpressionVariable( TypeSyntax typeSyntax, string name, SyntaxReference syntax, - Location location) - : base(containingType, modifiers, name, syntax, location) + TextSpan locationSpan) + : base(containingType, modifiers, name, syntax, locationSpan) { Debug.Assert(DeclaredAccessibility == Accessibility.Private); _typeSyntaxOpt = typeSyntax?.GetReference(); @@ -43,7 +44,7 @@ internal static GlobalExpressionVariable Create( TypeSyntax typeSyntax, string name, SyntaxNode syntax, - Location location, + TextSpan locationSpan, FieldSymbol containingFieldOpt, SyntaxNode nodeToBind) { @@ -51,8 +52,8 @@ internal static GlobalExpressionVariable Create( var syntaxReference = syntax.GetReference(); return (typeSyntax == null || typeSyntax.SkipScoped(out _).SkipRef().IsVar) - ? new InferrableGlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, location, containingFieldOpt, nodeToBind) - : new GlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, location); + ? new InferrableGlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, locationSpan, containingFieldOpt, nodeToBind) + : new GlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, locationSpan); } protected override SyntaxList AttributeDeclarationSyntaxList => default(SyntaxList); @@ -169,10 +170,10 @@ internal InferrableGlobalExpressionVariable( TypeSyntax typeSyntax, string name, SyntaxReference syntax, - Location location, + TextSpan locationSpan, FieldSymbol containingFieldOpt, SyntaxNode nodeToBind) - : base(containingType, modifiers, typeSyntax, name, syntax, location) + : base(containingType, modifiers, typeSyntax, name, syntax, locationSpan) { Debug.Assert(nodeToBind.Kind() == SyntaxKind.VariableDeclarator || nodeToBind is ExpressionSyntax); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index 89a6dbdb8a619..e54c7008482b0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -303,6 +303,8 @@ public override bool IsExtensionMethod public override ImmutableArray Locations => ImmutableArray.Create(Syntax.Identifier.GetLocation()); + public override Location? TryGetFirstLocation() => Syntax.Identifier.GetLocation(); + internal override bool GenerateDebugInfo => true; public override ImmutableArray RefCustomModifiers => ImmutableArray.Empty; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs index 45500e7957e5a..81b6d58b15362 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs @@ -46,7 +46,7 @@ public static SourceEnumConstantSymbol CreateImplicitValuedConstant( } protected SourceEnumConstantSymbol(SourceMemberContainerTypeSymbol containingEnum, EnumMemberDeclarationSyntax syntax, BindingDiagnosticBag diagnostics) - : base(containingEnum, syntax.Identifier.ValueText, syntax.GetReference(), syntax.Identifier.GetLocation()) + : base(containingEnum, syntax.Identifier.ValueText, syntax.GetReference(), syntax.Identifier.Span) { if (this.Name == WellKnownMemberNames.EnumBackingFieldName) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs index e97a17ce96331..17ff8487a2f1c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs @@ -11,6 +11,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -166,7 +167,7 @@ internal sealed override bool HasRuntimeSpecialName internal abstract class SourceFieldSymbolWithSyntaxReference : SourceFieldSymbol { private readonly string _name; - private readonly Location _location; + private readonly TextSpan _locationSpan; private readonly SyntaxReference _syntaxReference; private string _lazyDocComment; @@ -174,16 +175,15 @@ internal abstract class SourceFieldSymbolWithSyntaxReference : SourceFieldSymbol private ConstantValue _lazyConstantEarlyDecodingValue = Microsoft.CodeAnalysis.ConstantValue.Unset; private ConstantValue _lazyConstantValue = Microsoft.CodeAnalysis.ConstantValue.Unset; - protected SourceFieldSymbolWithSyntaxReference(SourceMemberContainerTypeSymbol containingType, string name, SyntaxReference syntax, Location location) + protected SourceFieldSymbolWithSyntaxReference(SourceMemberContainerTypeSymbol containingType, string name, SyntaxReference syntax, TextSpan locationSpan) : base(containingType) { Debug.Assert(name != null); Debug.Assert(syntax != null); - Debug.Assert(location != null); _name = name; _syntaxReference = syntax; - _location = location; + _locationSpan = locationSpan; } public SyntaxTree SyntaxTree @@ -211,33 +211,19 @@ public sealed override string Name } internal override LexicalSortKey GetLexicalSortKey() - { - return new LexicalSortKey(_location, this.DeclaringCompilation); - } + => new LexicalSortKey(_syntaxReference, this.DeclaringCompilation); + + public override Location TryGetFirstLocation() + => _syntaxReference.SyntaxTree.GetLocation(_locationSpan); public sealed override ImmutableArray Locations - { - get - { - return ImmutableArray.Create(_location); - } - } + => ImmutableArray.Create(GetFirstLocation()); internal sealed override Location ErrorLocation - { - get - { - return _location; - } - } + => GetFirstLocation(); public sealed override ImmutableArray DeclaringSyntaxReferences - { - get - { - return ImmutableArray.Create(_syntaxReference); - } - } + => ImmutableArray.Create(_syntaxReference); public sealed override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken)) { @@ -341,7 +327,7 @@ private void BindConstantValueIfNecessary(bool earlyDecodingWellKnownAttributes, var diagnostics = BindingDiagnosticBag.GetInstance(); if (startsCycle) { - diagnostics.Add(ErrorCode.ERR_CircConstValue, _location, this); + diagnostics.Add(ErrorCode.ERR_CircConstValue, GetFirstLocation(), this); } var value = MakeConstantValue(builder, earlyDecodingWellKnownAttributes, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index ccbacc8bf13bd..746a884447a8e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -8,11 +8,14 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; +#if DEBUG +using System.Runtime.CompilerServices; +#endif + namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// @@ -28,7 +31,6 @@ internal class SourceLocalSymbol : LocalSymbol private readonly Symbol _containingSymbol; private readonly SyntaxToken _identifierToken; - private readonly ImmutableArray _locations; private readonly TypeSyntax _typeSyntax; private readonly RefKind _refKind; private readonly LocalDeclarationKind _declarationKind; @@ -69,9 +71,6 @@ private SourceLocalSymbol( : isScoped ? ScopedKind.ScopedValue : ScopedKind.None; this._declarationKind = declarationKind; - - // create this eagerly as it will always be needed for the EnsureSingleDefinition - _locations = ImmutableArray.Create(identifierToken.GetLocation()); } /// @@ -404,18 +403,16 @@ internal void SetTypeWithAnnotations(TypeWithAnnotations newType) } } + public override Location TryGetFirstLocation() + => _identifierToken.GetLocation(); + /// /// Gets the locations where the local symbol was originally defined in source. /// There should not be local symbols from metadata, and there should be only one local variable declared. /// TODO: check if there are multiple same name local variables - error symbol or local symbol? /// public override ImmutableArray Locations - { - get - { - return _locations; - } - } + => ImmutableArray.Create(GetFirstLocation()); internal sealed override SyntaxNode GetDeclaratorSyntax() { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs index 3c8930fb110f2..54bf08a5356cb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs @@ -26,8 +26,8 @@ internal SourceMemberFieldSymbol( DeclarationModifiers modifiers, string name, SyntaxReference syntax, - Location location) - : base(containingType, name, syntax, location) + TextSpan locationSpan) + : base(containingType, name, syntax, locationSpan) { _modifiers = modifiers; } @@ -338,7 +338,7 @@ internal SourceMemberFieldSymbolFromDeclarator( DeclarationModifiers modifiers, bool modifierErrors, BindingDiagnosticBag diagnostics) - : base(containingType, modifiers, declarator.Identifier.ValueText, declarator.GetReference(), declarator.Identifier.GetLocation()) + : base(containingType, modifiers, declarator.Identifier.ValueText, declarator.GetReference(), declarator.Identifier.Span) { _hasInitializer = declarator.Initializer != null; From 75e2c296e71f053017cf1afb3e16c2922b8e7a78 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 12 Apr 2023 16:47:58 -0500 Subject: [PATCH 31/69] Incrementally update dependency graph for new cases * Removing all references from a project * Adding references to a project that did not have any --- .../Solution/ProjectDependencyGraph.cs | 11 ++ ...endencyGraph_RemoveAllProjectReferences.cs | 124 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_RemoveAllProjectReferences.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index cf16b22f5fcb4..4593d8b9c5c70 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs @@ -128,6 +128,17 @@ internal ProjectDependencyGraph WithProjectReferences(ProjectId projectId, IRead { Contract.ThrowIfFalse(_projectIds.Contains(projectId)); + if (!_referencesMap.ContainsKey(projectId)) + { + // This project doesn't have any references currently, so we delegate to WithAdditionalProjectReferences + return WithAdditionalProjectReferences(projectId, projectReferences); + } + else if (projectReferences.Count == 0) + { + // We are removing all project references; do so directly + return WithAllProjectReferencesRemoved(projectId); + } + // This method we can't optimize very well: changing project references arbitrarily could invalidate pretty much anything. // The only thing we can reuse is our actual map of project references for all the other projects, so we'll do that. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_RemoveAllProjectReferences.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_RemoveAllProjectReferences.cs new file mode 100644 index 0000000000000..22deeab440b73 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_RemoveAllProjectReferences.cs @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + public partial class ProjectDependencyGraph + { + internal ProjectDependencyGraph WithAllProjectReferencesRemoved(ProjectId projectId) + { + Contract.ThrowIfFalse(_projectIds.Contains(projectId)); + + if (!_referencesMap.TryGetValue(projectId, out var referencedProjectIds)) + return this; + + // Removing a project reference doesn't change the set of projects + var projectIds = _projectIds; + + // Incrementally update the graph + var referencesMap = ComputeNewReferencesMapForRemovedAllProjectReferences(_referencesMap, projectId); + var reverseReferencesMap = ComputeNewReverseReferencesMapForRemovedAllProjectReferences(_lazyReverseReferencesMap, projectId, referencedProjectIds); + var transitiveReferencesMap = ComputeNewTransitiveReferencesMapForRemovedAllProjectReferences(_transitiveReferencesMap, projectId, referencedProjectIds); + var reverseTransitiveReferencesMap = ComputeNewReverseTransitiveReferencesMapForRemovedAllProjectReferences(_reverseTransitiveReferencesMap, projectId); + + return new ProjectDependencyGraph( + projectIds, + referencesMap, + reverseReferencesMap, + transitiveReferencesMap, + reverseTransitiveReferencesMap, + topologicallySortedProjects: default, + dependencySets: default); + } + + private static ImmutableDictionary> ComputeNewReferencesMapForRemovedAllProjectReferences( + ImmutableDictionary> existingForwardReferencesMap, + ProjectId projectId) + { + // Projects with no references do not have an entry in the forward references map + return existingForwardReferencesMap.Remove(projectId); + } + + /// + /// Computes a new for the removal of all project references from a + /// project. + /// + /// The prior to the removal, + /// or if the reverse references map was not computed for the prior graph. + /// The project ID from which a project reference is being removed. + /// The targets of the project references which are being removed. + /// The updated (complete) reverse references map, or if the reverse references + /// map could not be incrementally updated. + private static ImmutableDictionary>? ComputeNewReverseReferencesMapForRemovedAllProjectReferences( + ImmutableDictionary>? existingReverseReferencesMap, + ProjectId projectId, + ImmutableHashSet referencedProjectIds) + { + if (existingReverseReferencesMap is null) + { + return null; + } + + var builder = existingReverseReferencesMap.ToBuilder(); + foreach (var referencedProjectId in referencedProjectIds) + { + builder.MultiRemove(referencedProjectId, projectId); + } + + return builder.ToImmutable(); + } + + private static ImmutableDictionary> ComputeNewTransitiveReferencesMapForRemovedAllProjectReferences( + ImmutableDictionary> existingTransitiveReferencesMap, + ProjectId projectId, + ImmutableHashSet referencedProjectIds) + { + var builder = existingTransitiveReferencesMap.ToBuilder(); + + // Invalidate the transitive references from every project referencing the changed project (transitively) + foreach (var (project, references) in existingTransitiveReferencesMap) + { + if (!references.Contains(projectId)) + { + // This is the forward-references-equivalent of the optimization in the update of reverse transitive + // references. + continue; + } + + Debug.Assert(references.IsSupersetOf(referencedProjectIds)); + builder.Remove(project); + } + + // Invalidate the transitive references from the changed project + builder.Remove(projectId); + + return builder.ToImmutable(); + } + + private static ImmutableDictionary> ComputeNewReverseTransitiveReferencesMapForRemovedAllProjectReferences( + ImmutableDictionary> existingReverseTransitiveReferencesMap, + ProjectId projectId) + { + var builder = existingReverseTransitiveReferencesMap.ToBuilder(); + + // Invalidate the transitive reverse references from every project previously referenced by the original + // project (transitively). + foreach (var (project, references) in existingReverseTransitiveReferencesMap) + { + if (!references.Contains(projectId)) + { + continue; + } + + builder.Remove(project); + } + + return builder.ToImmutable(); + } + } +} From b773a676a8234ad0fe72a38b8e1a4b869f8e3cad Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 15:19:34 -0700 Subject: [PATCH 32/69] Add unit test --- .../CodeActions/CodeActionResolveTests.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index deaa7dcf9b2f1..5d2d4a9905863 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -136,6 +136,109 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } + [WpfTheory, CombinatorialData] + public async Task Test(bool mutatingLspWorkspace) + { + var xmlWorkspace = @" + + + class C +{ + public static readonly int {|caret:_value|} = 10; +} + + + + + + +"; + await using var testLspServer = await CreateXmlTestLspServerAsync(xmlWorkspace, mutatingLspWorkspace); + + var unresolvedCodeAction = CodeActionsTests.CreateCodeAction( + title: string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), + kind: CodeActionKind.Refactor, + children: Array.Empty(), + data: CreateCodeActionResolveData( + string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), + testLspServer.GetLocations("caret").Single()), + priority: VSInternalPriorityLevel.Normal, + groupName: "Roslyn2", + applicableRange: new LSP.Range { Start = new Position { Line = 2, Character = 33 }, End = new Position { Line = 39, Character = 2 } }, + diagnostics: null); + + var actualResolvedAction = await RunGetCodeActionResolveAsync(testLspServer, unresolvedCodeAction); + var edits = new TextEdit[] + { + new TextEdit() + { + NewText = "private", + Range = new LSP.Range() + { + Start = new Position + { + Line = 2, + Character = 4 + }, + End = new Position + { + Line = 2, + Character = 10 + } + } + }, + new TextEdit + { + NewText = string.Empty, + Range = new LSP.Range + { + Start = new Position + { + Line = 2, + Character = 31 + }, + End = new Position + { + Line = 2, + Character = 32 + } + } + }, + new TextEdit + { + NewText = @" + + public static int Value => value;", + Range = new LSP.Range + { + Start = new Position + { + Line = 2, + Character = 43 + }, + End = new Position + { + Line = 2, + Character = 43 + } + } + } + }; + var expectedCodeAction = CodeActionsTests.CreateCodeAction( + title: string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), + kind: CodeActionKind.Refactor, + children: Array.Empty(), + data: CreateCodeActionResolveData( + string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), + testLspServer.GetLocations("caret").Single()), + priority: VSInternalPriorityLevel.Normal, + groupName: "Roslyn2", + applicableRange: new LSP.Range { Start = new Position { Line = 2, Character = 33 }, End = new Position { Line = 39, Character = 2 } }, + diagnostics: null, + edit: GenerateWorkspaceEdit(testLspServer.GetLocations("caret"), edits)); + AssertJsonEquals(expectedCodeAction, actualResolvedAction); + } + private static async Task RunGetCodeActionResolveAsync( TestLspServer testLspServer, VSInternalCodeAction unresolvedCodeAction) From 48c863aad7c578409012ebbef4e7cf99fc9637e4 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 12 Apr 2023 15:47:43 -0700 Subject: [PATCH 33/69] Avoid allocating UncommonData for common failure case (#67765) --- .../Semantics/Conversions/Conversion.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index 1592f2bac88c3..6f62adbf611d9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -25,6 +25,13 @@ namespace Microsoft.CodeAnalysis.CSharp // in uncommon cases an instance of this class is attached to the conversion. private class UncommonData { + public static readonly UncommonData NoApplicableOperators = new UncommonData( + isExtensionMethod: false, + isArrayIndex: false, + conversionResult: UserDefinedConversionResult.NoApplicableOperators(ImmutableArray.Empty), + conversionMethod: null, + nestedConversions: default); + public UncommonData( bool isExtensionMethod, bool isArrayIndex, @@ -107,12 +114,14 @@ internal Conversion(UserDefinedConversionResult conversionResult, bool isImplici ? ConversionKind.NoConversion : isImplicit ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined; - _uncommonData = new UncommonData( - isExtensionMethod: false, - isArrayIndex: false, - conversionResult: conversionResult, - conversionMethod: null, - nestedConversions: default); + _uncommonData = conversionResult.Kind == UserDefinedConversionResultKind.NoApplicableOperators && conversionResult.Results.IsEmpty + ? UncommonData.NoApplicableOperators + : new UncommonData( + isExtensionMethod: false, + isArrayIndex: false, + conversionResult: conversionResult, + conversionMethod: null, + nestedConversions: default); } // For the method group, lambda and anonymous method conversions From 9d28cebc000ea209e0513cc5e68bcb00ef6a50dd Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 12 Apr 2023 16:06:43 -0700 Subject: [PATCH 34/69] Cache constructed Nullable types for built-ins and enums (#67766) --- .../BinaryOperatorOverloadResolution.cs | 5 ++- .../UnaryOperatorOverloadResolution.cs | 2 +- .../Portable/Compilation/BuiltInOperators.cs | 35 +++++++++---------- .../Portable/Compilation/CSharpCompilation.cs | 13 +++++++ .../LocalRewriter_BinaryOperator.cs | 3 +- .../LocalRewriter_UnaryOperator.cs | 2 +- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs index b8599bddf7e3f..e5aba45435135 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs @@ -475,9 +475,8 @@ private void GetEnumOperation(BinaryOperatorKind kind, TypeSymbol enumType, Boun Debug.Assert((object)underlying != null); Debug.Assert(underlying.SpecialType != SpecialType.None); - var nullable = Compilation.GetSpecialType(SpecialType.System_Nullable_T); - var nullableEnum = nullable.Construct(enumType); - var nullableUnderlying = nullable.Construct(underlying); + var nullableEnum = MakeNullable(enumType); + var nullableUnderlying = MakeNullable(underlying); switch (kind) { diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs index f849ec6edd41b..4c6c2656b402f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs @@ -17,7 +17,7 @@ internal sealed partial class OverloadResolution { private NamedTypeSymbol MakeNullable(TypeSymbol type) { - return Compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(type); + return Compilation.GetOrCreateNullableType(type); } public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, bool isChecked, BoundExpression operand, UnaryOperatorOverloadResolutionResult result, ref CompoundUseSiteInfo useSiteInfo) diff --git a/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs b/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs index 8b4d330e83a70..0cc6a7bffb1f9 100644 --- a/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs +++ b/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs @@ -281,7 +281,7 @@ internal UnaryOperatorSignature GetSignature(UnaryOperatorKind kind) if (kind.IsLifted()) { - opType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(opType); + opType = _compilation.GetOrCreateNullableType(opType); } return new UnaryOperatorSignature(kind, opType, opType); @@ -723,7 +723,7 @@ internal BinaryOperatorSignature GetSignature(BinaryOperatorKind kind) TypeSymbol rightType = _compilation.GetSpecialType(SpecialType.System_Int32); if (kind.IsLifted()) { - rightType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(rightType); + rightType = _compilation.GetOrCreateNullableType(rightType); } return new BinaryOperatorSignature(kind, left, rightType, left); @@ -837,23 +837,22 @@ private TypeSymbol LiftedType(BinaryOperatorKind kind) { Debug.Assert(kind.IsLifted()); - var nullable = _compilation.GetSpecialType(SpecialType.System_Nullable_T); - - switch (kind.OperandTypes()) + var typeArgument = kind.OperandTypes() switch { - case BinaryOperatorKind.Int: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Int32)); - case BinaryOperatorKind.UInt: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_UInt32)); - case BinaryOperatorKind.Long: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Int64)); - case BinaryOperatorKind.ULong: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_UInt64)); - case BinaryOperatorKind.NInt: return nullable.Construct(_compilation.CreateNativeIntegerTypeSymbol(signed: true)); - case BinaryOperatorKind.NUInt: return nullable.Construct(_compilation.CreateNativeIntegerTypeSymbol(signed: false)); - case BinaryOperatorKind.Float: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Single)); - case BinaryOperatorKind.Double: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Double)); - case BinaryOperatorKind.Decimal: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Decimal)); - case BinaryOperatorKind.Bool: return nullable.Construct(_compilation.GetSpecialType(SpecialType.System_Boolean)); - } - Debug.Assert(false, "Bad operator kind in lifted type"); - return null; + BinaryOperatorKind.Int => _compilation.GetSpecialType(SpecialType.System_Int32), + BinaryOperatorKind.UInt => _compilation.GetSpecialType(SpecialType.System_UInt32), + BinaryOperatorKind.Long => _compilation.GetSpecialType(SpecialType.System_Int64), + BinaryOperatorKind.ULong => _compilation.GetSpecialType(SpecialType.System_UInt64), + BinaryOperatorKind.NInt => _compilation.CreateNativeIntegerTypeSymbol(signed: true), + BinaryOperatorKind.NUInt => _compilation.CreateNativeIntegerTypeSymbol(signed: false), + BinaryOperatorKind.Float => _compilation.GetSpecialType(SpecialType.System_Single), + BinaryOperatorKind.Double => _compilation.GetSpecialType(SpecialType.System_Double), + BinaryOperatorKind.Decimal => _compilation.GetSpecialType(SpecialType.System_Decimal), + BinaryOperatorKind.Bool => _compilation.GetSpecialType(SpecialType.System_Boolean), + var v => throw ExceptionUtilities.UnexpectedValue(v), + }; + + return _compilation.GetOrCreateNullableType(typeArgument); } internal static bool IsValidObjectEquality(Conversions Conversions, TypeSymbol leftType, bool leftIsNull, bool leftIsDefault, TypeSymbol rightType, bool rightIsNull, bool rightIsDefault, ref CompoundUseSiteInfo useSiteInfo) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 5f5c1a4c3d414..d7eeb560d938f 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -16,6 +16,7 @@ using Microsoft.Cci; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -1565,6 +1566,18 @@ internal AliasSymbol GlobalNamespaceAlias return result; } + /// + /// Cache of T to Nullable<T>. + /// + private ImmutableSegmentedDictionary _typeToNullableVersion = ImmutableSegmentedDictionary.Empty; + + internal NamedTypeSymbol GetOrCreateNullableType(TypeSymbol typeArgument) + => RoslynImmutableInterlocked.GetOrAdd( + ref _typeToNullableVersion, + typeArgument, + static (typeArgument, @this) => @this.GetSpecialType(SpecialType.System_Nullable_T).Construct(typeArgument), + this); + /// /// Get the symbol for the predefined type member from the COR Library referenced by this compilation. /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs index 55e82a0530129..0fd96f0aaf08d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs @@ -1558,9 +1558,8 @@ private BoundExpression CaptureExpressionInTempIfNeeded( private BoundExpression MakeNewNullableBoolean(SyntaxNode syntax, bool? value) { - NamedTypeSymbol nullableType = _compilation.GetSpecialType(SpecialType.System_Nullable_T); TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean); - NamedTypeSymbol nullableBoolType = nullableType.Construct(boolType); + NamedTypeSymbol nullableBoolType = _compilation.GetOrCreateNullableType(boolType); if (value == null) { return new BoundDefaultExpression(syntax, nullableBoolType); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index aefd2df3998c4..fb537a77a0ee1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -734,7 +734,7 @@ private BoundExpression MakeBuiltInIncrementOperator(BoundIncrementOperator node if (binaryOperatorKind.IsLifted()) { - binaryOperandType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(binaryOperandType); + binaryOperandType = _compilation.GetOrCreateNullableType(binaryOperandType); MethodSymbol ctor = UnsafeGetNullableMethod(node.Syntax, binaryOperandType, SpecialMember.System_Nullable_T__ctor); boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, boundOne); } From 0bbb08f4c2dd813c28e29a3074ac72cfc6296fd3 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 12 Apr 2023 16:25:02 -0700 Subject: [PATCH 35/69] Call WithMergedLinkedFileChangesAsync --- .../CodeActions/CodeActionResolveHandler.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 86d2363a7e478..507f302ff81f3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -108,6 +108,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) } var changes = applyChangesOperation.ChangedSolution.GetChanges(solution); + var newSolution = await applyChangesOperation.ChangedSolution.WithMergedLinkedFileChangesAsync(solution, changes, cancellationToken: cancellationToken).ConfigureAwait(false); + changes = newSolution.GetChanges(solution); + var projectChanges = changes.GetProjectChanges(); // Don't apply changes in the presence of any non-document changes for now. Note though that LSP does @@ -204,34 +207,34 @@ await AddTextDocumentDeletionsAsync( // Added documents await AddTextDocumentAdditionsAsync( projectChanges.SelectMany(pc => pc.GetAddedDocuments()), - applyChangesOperation.ChangedSolution.GetDocument).ConfigureAwait(false); + newSolution.GetDocument).ConfigureAwait(false); // Added analyzer config documents await AddTextDocumentAdditionsAsync( projectChanges.SelectMany(pc => pc.GetAddedAnalyzerConfigDocuments()), - applyChangesOperation.ChangedSolution.GetAnalyzerConfigDocument).ConfigureAwait(false); + newSolution.GetAnalyzerConfigDocument).ConfigureAwait(false); // Added additional documents await AddTextDocumentAdditionsAsync( projectChanges.SelectMany(pc => pc.GetAddedAdditionalDocuments()), - applyChangesOperation.ChangedSolution.GetAdditionalDocument).ConfigureAwait(false); + newSolution.GetAdditionalDocument).ConfigureAwait(false); // Changed documents await AddTextDocumentEditsAsync( projectChanges.SelectMany(pc => pc.GetChangedDocuments()), - applyChangesOperation.ChangedSolution.GetDocument, + newSolution.GetDocument, solution.GetDocument).ConfigureAwait(false); // Changed analyzer config documents await AddTextDocumentEditsAsync( projectChanges.SelectMany(pc => pc.GetChangedAnalyzerConfigDocuments()), - applyChangesOperation.ChangedSolution.GetAnalyzerConfigDocument, + newSolution.GetAnalyzerConfigDocument, solution.GetAnalyzerConfigDocument).ConfigureAwait(false); // Changed additional documents await AddTextDocumentEditsAsync( projectChanges.SelectMany(pc => pc.GetChangedAdditionalDocuments()), - applyChangesOperation.ChangedSolution.GetAdditionalDocument, + newSolution.GetAdditionalDocument, solution.GetAdditionalDocument).ConfigureAwait(false); } From b017edab4f2f85cb41b8827e8d51cf97f7f9fb83 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:03:18 -0700 Subject: [PATCH 36/69] Simpler way of waiting for add-using tasks to complete --- .../AbstractAddImportFeatureService.cs | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 08effecb82d7c..4158b4ad145fa 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -221,12 +221,11 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( var viableUnreferencedProjects = GetViableUnreferencedProjects(project); // Search all unreferenced projects in parallel. - var findTasks = new HashSet>>(); + using var _ = ArrayBuilder.GetInstance(out var findTasks); // Create another cancellation token so we can both search all projects in parallel, // but also stop any searches once we get enough results. - using var nestedTokenSource = new CancellationTokenSource(); - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(nestedTokenSource.Token, cancellationToken); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); foreach (var unreferencedProject in viableUnreferencedProjects) { @@ -237,11 +236,12 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( // direct references. i.e. we don't want to search in its metadata references // or in the projects it references itself. We'll be searching those entities // individually. - findTasks.Add(finder.FindInSourceSymbolsInProjectAsync( - projectToAssembly, unreferencedProject, exact, linkedTokenSource.Token)); + findTasks.Add(ProcessReferencesTask( + allSymbolReferences, maxResults, linkedTokenSource, + finder.FindInSourceSymbolsInProjectAsync(projectToAssembly, unreferencedProject, exact, linkedTokenSource.Token))); } - await WaitForTasksAsync(allSymbolReferences, maxResults, findTasks, nestedTokenSource, cancellationToken).ConfigureAwait(false); + await Task.WhenAll(findTasks).ConfigureAwait(false); } private async Task FindResultsInUnreferencedMetadataSymbolsAsync( @@ -265,12 +265,11 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( var newReferences = GetUnreferencedMetadataReferences(project, seenReferences); // Search all metadata references in parallel. - var findTasks = new HashSet>>(); + using var _ = ArrayBuilder.GetInstance(out var findTasks); // Create another cancellation token so we can both search all projects in parallel, // but also stop any searches once we get enough results. - using var nestedTokenSource = new CancellationTokenSource(); - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(nestedTokenSource.Token, cancellationToken); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); foreach (var (referenceProject, reference) in newReferences) { @@ -281,12 +280,13 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( // Second, the SymbolFinder API doesn't even support searching them. if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) { - findTasks.Add(finder.FindInMetadataSymbolsAsync( - assembly, referenceProject, reference, exact, linkedTokenSource.Token)); + findTasks.Add(ProcessReferencesTask( + allSymbolReferences, maxResults, linkedTokenSource, + finder.FindInMetadataSymbolsAsync(assembly, referenceProject, reference, exact, linkedTokenSource.Token))); } } - await WaitForTasksAsync(allSymbolReferences, maxResults, findTasks, nestedTokenSource, cancellationToken).ConfigureAwait(false); + await Task.WhenAll(findTasks).ConfigureAwait(false); } /// @@ -321,40 +321,36 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( return result.ToImmutableAndFree(); } - private static async Task WaitForTasksAsync( +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + private static async Task ProcessReferencesTask( ArrayBuilder allSymbolReferences, int maxResults, - HashSet>> findTasks, - CancellationTokenSource nestedTokenSource, - CancellationToken cancellationToken) + CancellationTokenSource linkedTokenSource, + Task> task) { - try + // Wait for either the task to finish, or the linked token to fire. + var finishedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, linkedTokenSource.Token)).ConfigureAwait(false); + if (finishedTask != task) { - while (findTasks.Count > 0) - { - // Keep on looping through the 'find' tasks, processing each when they finish. - cancellationToken.ThrowIfCancellationRequested(); - var doneTask = await Task.WhenAny(findTasks).ConfigureAwait(false); - - // One of the tasks finished. Remove it from the list we're waiting on. - findTasks.Remove(doneTask); + linkedTokenSource.Token.ThrowIfCancellationRequested(); + return; + } - // Add its results to the final result set we're keeping. - AddRange(allSymbolReferences, await doneTask.ConfigureAwait(false), maxResults); + var result = await task.ConfigureAwait(false); + var count = AddRange(allSymbolReferences, result, maxResults); - // Once we get enough, just stop. - if (allSymbolReferences.Count >= maxResults) - { - return; - } - } - } - finally + if (count >= maxResults) { - // Cancel any nested work that's still happening. - nestedTokenSource.Cancel(); + try + { + linkedTokenSource.Cancel(); + } + catch (ObjectDisposedException) + { + } } } +#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods /// /// We ignore references that are in a directory that contains the names @@ -456,10 +452,14 @@ private static HashSet GetViableUnreferencedProjects(Project project) return viableProjects; } - private static void AddRange(ArrayBuilder allSymbolReferences, ImmutableArray proposedReferences, int maxResults) + private static int AddRange(ArrayBuilder allSymbolReferences, ImmutableArray proposedReferences, int maxResults) where TReference : Reference { - allSymbolReferences.AddRange(proposedReferences.Take(maxResults - allSymbolReferences.Count)); + lock (allSymbolReferences) + { + allSymbolReferences.AddRange(proposedReferences.Take(maxResults - allSymbolReferences.Count)); + return allSymbolReferences.Count; + } } protected static bool IsViableExtensionMethod(IMethodSymbol method, ITypeSymbol receiver) From 4202cb7560bd3fc75ed08176e3e616172fe30212 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:03:52 -0700 Subject: [PATCH 37/69] Simplify --- .../Core/Portable/AddImport/AbstractAddImportFeatureService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 4158b4ad145fa..bfc33d08e3292 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -331,10 +331,7 @@ private static async Task ProcessReferencesTask( // Wait for either the task to finish, or the linked token to fire. var finishedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, linkedTokenSource.Token)).ConfigureAwait(false); if (finishedTask != task) - { - linkedTokenSource.Token.ThrowIfCancellationRequested(); return; - } var result = await task.ConfigureAwait(false); var count = AddRange(allSymbolReferences, result, maxResults); From a838bab2125db17827a2f66170e23ad42b5aadb2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:04:17 -0700 Subject: [PATCH 38/69] Simplify --- .../AbstractAddImportFeatureService.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index bfc33d08e3292..7001466e3f6aa 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -330,20 +330,20 @@ private static async Task ProcessReferencesTask( { // Wait for either the task to finish, or the linked token to fire. var finishedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, linkedTokenSource.Token)).ConfigureAwait(false); - if (finishedTask != task) - return; - - var result = await task.ConfigureAwait(false); - var count = AddRange(allSymbolReferences, result, maxResults); - - if (count >= maxResults) + if (finishedTask == task) { - try - { - linkedTokenSource.Cancel(); - } - catch (ObjectDisposedException) + var result = await task.ConfigureAwait(false); + var count = AddRange(allSymbolReferences, result, maxResults); + + if (count >= maxResults) { + try + { + linkedTokenSource.Cancel(); + } + catch (ObjectDisposedException) + { + } } } } From 3c0871dac9960f3a75fbc13668c5fb7203490872 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:07:19 -0700 Subject: [PATCH 39/69] Simplify --- .../AddImport/AbstractAddImportFeatureService.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 7001466e3f6aa..e264174ee624f 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -97,7 +97,6 @@ private async Task> GetFixesInCurrentProcessAsy using var _ = ArrayBuilder.GetInstance(out var result); if (node != null) { - using (Logger.LogBlock(FunctionId.Refactoring_AddImport, cancellationToken)) { if (!cancellationToken.IsCancellationRequested) @@ -214,9 +213,7 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( // If we didn't find enough hits searching just in the project, then check // in any unreferenced projects. if (allSymbolReferences.Count >= maxResults) - { return; - } var viableUnreferencedProjects = GetViableUnreferencedProjects(project); @@ -249,12 +246,10 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( Project project, ArrayBuilder allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { + // Only do this if none of the project searches produced any results. We may have a + // lot of metadata to search through, and it would be good to avoid that if we can. if (allSymbolReferences.Count > 0) - { - // Only do this if none of the project searches produced any results. We may have a - // lot of metadata to search through, and it would be good to avoid that if we can. return; - } // Keep track of the references we've seen (so that we don't process them multiple times // across many sibling projects). Prepopulate it with our own metadata references since From 0a6e3882315a87a25fbfe90f1e0eabc0f2985df4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:13:21 -0700 Subject: [PATCH 40/69] Remove locks --- .../AbstractAddImportFeatureService.cs | 31 ++++++++++--------- ...olReferenceFinder_PackageAssemblySearch.cs | 27 ++++++++-------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index e264174ee624f..cf3d5e5307ebd 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -7,14 +7,11 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; @@ -167,7 +164,7 @@ private async Task> FindResultsAsync( ConcurrentDictionary referenceToCompilation, Project project, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allReferences); + var allReferences = new ConcurrentStack(); // First search the current project to see if any symbols (source or metadata) match the // search string. @@ -194,11 +191,14 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } } - return allReferences.ToImmutable(); + while (allReferences.Count > maxResults) + allReferences.TryPop(out _); + + return allReferences.ToImmutableArray(); } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( - ArrayBuilder allSymbolReferences, int maxResults, SymbolReferenceFinder finder, + ConcurrentStack allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { var references = await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false); @@ -207,7 +207,7 @@ private static async Task FindResultsInAllSymbolsInStartingProjectAsync( private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( ConcurrentDictionary> projectToAssembly, - Project project, ArrayBuilder allSymbolReferences, int maxResults, + Project project, ConcurrentStack allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { // If we didn't find enough hits searching just in the project, then check @@ -243,7 +243,7 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( private async Task FindResultsInUnreferencedMetadataSymbolsAsync( ConcurrentDictionary referenceToCompilation, - Project project, ArrayBuilder allSymbolReferences, int maxResults, SymbolReferenceFinder finder, + Project project, ConcurrentStack allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { // Only do this if none of the project searches produced any results. We may have a @@ -318,7 +318,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods private static async Task ProcessReferencesTask( - ArrayBuilder allSymbolReferences, + ConcurrentStack allSymbolReferences, int maxResults, CancellationTokenSource linkedTokenSource, Task> task) @@ -328,9 +328,9 @@ private static async Task ProcessReferencesTask( if (finishedTask == task) { var result = await task.ConfigureAwait(false); - var count = AddRange(allSymbolReferences, result, maxResults); + AddRange(allSymbolReferences, result, maxResults); - if (count >= maxResults) + if (allSymbolReferences.Count >= maxResults) { try { @@ -444,13 +444,14 @@ private static HashSet GetViableUnreferencedProjects(Project project) return viableProjects; } - private static int AddRange(ArrayBuilder allSymbolReferences, ImmutableArray proposedReferences, int maxResults) + private static void AddRange(ConcurrentStack allSymbolReferences, ImmutableArray proposedReferences, int maxResults) where TReference : Reference { - lock (allSymbolReferences) + foreach (var reference in proposedReferences) { - allSymbolReferences.AddRange(proposedReferences.Take(maxResults - allSymbolReferences.Count)); - return allSymbolReferences.Count; + // It's fine that this is racey. Top level caller will still ensure we only have maxResults number of items. + if (allSymbolReferences.Count < maxResults) + allSymbolReferences.Push(reference); } } diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs index af318c6558c01..91274dea370b9 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.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.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; @@ -17,15 +18,13 @@ internal abstract partial class AbstractAddImportFeatureService allReferences, CancellationToken cancellationToken) + ConcurrentStack allReferences, CancellationToken cancellationToken) { + // Only do this if none of the project or metadata searches produced + // any results. We always consider source and local metadata to be + // better than any NuGet/assembly-reference results. if (allReferences.Count > 0) - { - // Only do this if none of the project or metadata searches produced - // any results. We always consider source and local metadata to be - // better than any NuGet/assembly-reference results. return; - } if (!_owner.CanAddImportForType(_diagnosticId, _node, out var nameNode)) { @@ -46,7 +45,7 @@ await FindNugetOrReferenceAssemblyTypeReferencesAsync( } private async Task FindNugetOrReferenceAssemblyTypeReferencesAsync( - ArrayBuilder allReferences, TSimpleNameSyntax nameNode, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, string name, int arity, bool inAttributeContext, CancellationToken cancellationToken) { @@ -63,7 +62,7 @@ await FindNugetOrReferenceAssemblyTypeReferencesWorkerAsync( } private async Task FindNugetOrReferenceAssemblyTypeReferencesWorkerAsync( - ArrayBuilder allReferences, TSimpleNameSyntax nameNode, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, string name, int arity, bool isAttributeSearch, CancellationToken cancellationToken) { if (_options.SearchOptions.SearchReferenceAssemblies) @@ -84,7 +83,7 @@ await FindNugetTypeReferencesAsync( } private async Task FindReferenceAssemblyTypeReferencesAsync( - ArrayBuilder allReferences, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, string name, int arity, @@ -110,7 +109,7 @@ await HandleReferenceAssemblyReferenceAsync( private async Task FindNugetTypeReferencesAsync( string sourceName, string sourceUrl, - ArrayBuilder allReferences, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, string name, int arity, @@ -132,7 +131,7 @@ private async Task FindNugetTypeReferencesAsync( } private async Task HandleReferenceAssemblyReferenceAsync( - ArrayBuilder allReferences, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, Project project, bool isAttributeSearch, @@ -154,20 +153,20 @@ private async Task HandleReferenceAssemblyReferenceAsync( } var desiredName = GetDesiredName(isAttributeSearch, result.TypeName); - allReferences.Add(new AssemblyReference( + allReferences.Push(new AssemblyReference( _owner, new SearchResult(desiredName, nameNode, result.ContainingNamespaceNames.ToReadOnlyList(), weight), result)); } private void HandleNugetReference( string source, - ArrayBuilder allReferences, + ConcurrentStack allReferences, TSimpleNameSyntax nameNode, bool isAttributeSearch, PackageWithTypeResult result, int weight) { var desiredName = GetDesiredName(isAttributeSearch, result.TypeName); - allReferences.Add(new PackageReference(_owner, + allReferences.Push(new PackageReference(_owner, new SearchResult(desiredName, nameNode, result.ContainingNamespaceNames.ToReadOnlyList(), weight), source, result.PackageName, result.Version)); } From 01fe4a51dd5f6fa10fdee642f8e744f6048ae622 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:14:52 -0700 Subject: [PATCH 41/69] Simplify --- .../AddImport/AbstractAddImportFeatureService.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index cf3d5e5307ebd..862f3ebb2c6cc 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -105,13 +105,15 @@ private async Task> GetFixesInCurrentProcessAsy document, semanticModel, diagnosticId, node, maxResults, symbolSearchService, options, packageSources, cancellationToken).ConfigureAwait(false); - // Nothing found at all. No need to proceed. foreach (var reference in allSymbolReferences) { cancellationToken.ThrowIfCancellationRequested(); var fixData = await reference.TryGetFixDataAsync(document, node, options.CleanupOptions, cancellationToken).ConfigureAwait(false); result.AddIfNotNull(fixData); + + if (result.Count > maxResults) + break; } } } @@ -138,9 +140,7 @@ private async Task> FindResultsAsync( // Look for exact matches first: var exactReferences = await FindResultsAsync(projectToAssembly, referenceToCompilation, project, maxResults, finder, exact: true, cancellationToken: cancellationToken).ConfigureAwait(false); if (exactReferences.Length > 0) - { return exactReferences; - } // No exact matches found. Fall back to fuzzy searching. // Only bother doing this for host workspaces. We don't want this for @@ -191,9 +191,6 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } } - while (allReferences.Count > maxResults) - allReferences.TryPop(out _); - return allReferences.ToImmutableArray(); } From 7ee35751a68d787d6b35edfdc107c22c27b05e08 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 18:16:54 -0700 Subject: [PATCH 42/69] Simplify --- .../AbstractAddImportFeatureService.cs | 18 +++++++----------- ...bolReferenceFinder_PackageAssemblySearch.cs | 18 +++++++++--------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 862f3ebb2c6cc..6635ba547c850 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -164,7 +164,7 @@ private async Task> FindResultsAsync( ConcurrentDictionary referenceToCompilation, Project project, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { - var allReferences = new ConcurrentStack(); + var allReferences = new ConcurrentQueue(); // First search the current project to see if any symbols (source or metadata) match the // search string. @@ -195,7 +195,7 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( - ConcurrentStack allSymbolReferences, int maxResults, SymbolReferenceFinder finder, + ConcurrentQueue allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { var references = await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false); @@ -204,7 +204,7 @@ private static async Task FindResultsInAllSymbolsInStartingProjectAsync( private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( ConcurrentDictionary> projectToAssembly, - Project project, ConcurrentStack allSymbolReferences, int maxResults, + Project project, ConcurrentQueue allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { // If we didn't find enough hits searching just in the project, then check @@ -240,7 +240,7 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( private async Task FindResultsInUnreferencedMetadataSymbolsAsync( ConcurrentDictionary referenceToCompilation, - Project project, ConcurrentStack allSymbolReferences, int maxResults, SymbolReferenceFinder finder, + Project project, ConcurrentQueue allSymbolReferences, int maxResults, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { // Only do this if none of the project searches produced any results. We may have a @@ -315,7 +315,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods private static async Task ProcessReferencesTask( - ConcurrentStack allSymbolReferences, + ConcurrentQueue allSymbolReferences, int maxResults, CancellationTokenSource linkedTokenSource, Task> task) @@ -441,15 +441,11 @@ private static HashSet GetViableUnreferencedProjects(Project project) return viableProjects; } - private static void AddRange(ConcurrentStack allSymbolReferences, ImmutableArray proposedReferences, int maxResults) + private static void AddRange(ConcurrentQueue allSymbolReferences, ImmutableArray proposedReferences, int maxResults) where TReference : Reference { foreach (var reference in proposedReferences) - { - // It's fine that this is racey. Top level caller will still ensure we only have maxResults number of items. - if (allSymbolReferences.Count < maxResults) - allSymbolReferences.Push(reference); - } + allSymbolReferences.Enqueue(reference); } protected static bool IsViableExtensionMethod(IMethodSymbol method, ITypeSymbol receiver) diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs index 91274dea370b9..a1284d2bf2ac5 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs @@ -18,7 +18,7 @@ internal abstract partial class AbstractAddImportFeatureService allReferences, CancellationToken cancellationToken) + ConcurrentQueue allReferences, CancellationToken cancellationToken) { // Only do this if none of the project or metadata searches produced // any results. We always consider source and local metadata to be @@ -45,7 +45,7 @@ await FindNugetOrReferenceAssemblyTypeReferencesAsync( } private async Task FindNugetOrReferenceAssemblyTypeReferencesAsync( - ConcurrentStack allReferences, TSimpleNameSyntax nameNode, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, string name, int arity, bool inAttributeContext, CancellationToken cancellationToken) { @@ -62,7 +62,7 @@ await FindNugetOrReferenceAssemblyTypeReferencesWorkerAsync( } private async Task FindNugetOrReferenceAssemblyTypeReferencesWorkerAsync( - ConcurrentStack allReferences, TSimpleNameSyntax nameNode, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, string name, int arity, bool isAttributeSearch, CancellationToken cancellationToken) { if (_options.SearchOptions.SearchReferenceAssemblies) @@ -83,7 +83,7 @@ await FindNugetTypeReferencesAsync( } private async Task FindReferenceAssemblyTypeReferencesAsync( - ConcurrentStack allReferences, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, string name, int arity, @@ -109,7 +109,7 @@ await HandleReferenceAssemblyReferenceAsync( private async Task FindNugetTypeReferencesAsync( string sourceName, string sourceUrl, - ConcurrentStack allReferences, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, string name, int arity, @@ -131,7 +131,7 @@ private async Task FindNugetTypeReferencesAsync( } private async Task HandleReferenceAssemblyReferenceAsync( - ConcurrentStack allReferences, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, Project project, bool isAttributeSearch, @@ -153,20 +153,20 @@ private async Task HandleReferenceAssemblyReferenceAsync( } var desiredName = GetDesiredName(isAttributeSearch, result.TypeName); - allReferences.Push(new AssemblyReference( + allReferences.Enqueue(new AssemblyReference( _owner, new SearchResult(desiredName, nameNode, result.ContainingNamespaceNames.ToReadOnlyList(), weight), result)); } private void HandleNugetReference( string source, - ConcurrentStack allReferences, + ConcurrentQueue allReferences, TSimpleNameSyntax nameNode, bool isAttributeSearch, PackageWithTypeResult result, int weight) { var desiredName = GetDesiredName(isAttributeSearch, result.TypeName); - allReferences.Push(new PackageReference(_owner, + allReferences.Enqueue(new PackageReference(_owner, new SearchResult(desiredName, nameNode, result.ContainingNamespaceNames.ToReadOnlyList(), weight), source, result.PackageName, result.Version)); } From 0102257cd81c4cffd6575ada670f00b891f17715 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 12 Apr 2023 18:42:33 -0700 Subject: [PATCH 43/69] Avoid realizing SyntaxReference[] in source methods (#67750) * Avoid realizing SyntaxReference[] in source methods * Update fields as well --- .../CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs | 3 +++ .../Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs | 3 ++- src/Compilers/CSharp/Portable/Symbols/Symbol.cs | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs index 17ff8487a2f1c..04279e41aaf83 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs @@ -225,6 +225,9 @@ internal sealed override Location ErrorLocation public sealed override ImmutableArray DeclaringSyntaxReferences => ImmutableArray.Create(_syntaxReference); + public override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default) + => IsDefinedInSourceTree(_syntaxReference, tree, definedWithinSpan); + public sealed override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken)) { ref var lazyDocComment = ref expandIncludes ? ref _lazyExpandedDocComment : ref _lazyDocComment; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs index aadde6cc87875..b107771ba450b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs @@ -620,8 +620,9 @@ public override bool IsDefinedInSourceTree( { // Since only the declaring (and not the implementing) part of a partial method appears in the member // list, we need to ensure we complete the implementation part when needed. + Debug.Assert(this.DeclaringSyntaxReferences.Length == 1); return - base.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken) || + IsDefinedInSourceTree(this.SyntaxRef, tree, definedWithinSpan) || this.SourcePartialImplementation?.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken) == true; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs index 734dba9e066e3..8f2c451aa7a85 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs @@ -829,8 +829,7 @@ internal bool Dangerous_IsFromSomeCompilation { cancellationToken.ThrowIfCancellationRequested(); - if (syntaxRef.SyntaxTree == tree && - (!definedWithinSpan.HasValue || syntaxRef.Span.IntersectsWith(definedWithinSpan.Value))) + if (IsDefinedInSourceTree(syntaxRef, tree, definedWithinSpan)) { return true; } @@ -839,6 +838,10 @@ internal bool Dangerous_IsFromSomeCompilation return false; } + protected static bool IsDefinedInSourceTree(SyntaxReference syntaxRef, SyntaxTree tree, TextSpan? definedWithinSpan) + => syntaxRef.SyntaxTree == tree && + (!definedWithinSpan.HasValue || syntaxRef.Span.IntersectsWith(definedWithinSpan.Value)); + internal static void ForceCompleteMemberByLocation(SourceLocation locationOpt, Symbol member, CancellationToken cancellationToken) { if (locationOpt == null || member.IsDefinedInSourceTree(locationOpt.SourceTree, locationOpt.SourceSpan, cancellationToken)) From 93cd10a5fdff679ecab004afb9e594c0be240b11 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 12 Apr 2023 22:41:05 -0700 Subject: [PATCH 44/69] Remove unnecessary Location object allocations when checking lang versions. (#67773) * Continued removal of location allocations * Continued removal of location allocations * Only compute once * Defer location allocation * Defer more location creation * Apply suggestions from code review * Update src/Compilers/CSharp/Portable/Binder/Binder.cs * Remove unnecessary loc allocs * Revert * revert * revert * revert --- .../Portable/Binder/Binder_AnonymousTypes.cs | 2 +- .../Portable/Binder/Binder_Attributes.cs | 3 +- .../CSharp/Portable/Binder/Binder_Await.cs | 2 +- .../Portable/Binder/Binder_Expressions.cs | 37 +++++---- .../CSharp/Portable/Binder/Binder_Lambda.cs | 10 +-- .../Portable/Binder/Binder_Operators.cs | 4 +- .../CSharp/Portable/Binder/Binder_Patterns.cs | 18 ++--- .../CSharp/Portable/Binder/Binder_Query.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 14 ++-- .../CSharp/Portable/Binder/Binder_Symbols.cs | 4 +- .../Portable/Binder/Binder_WithExpression.cs | 2 +- .../Portable/Binder/BindingDiagnosticBag.cs | 3 + .../Portable/Binder/SwitchBinder_Patterns.cs | 2 +- .../Declarations/SingleTypeDeclaration.cs | 2 +- .../CSharp/Portable/Errors/MessageID.cs | 76 ++++++++++++++++--- .../CSharp/Portable/Symbols/AliasSymbol.cs | 2 +- .../Portable/Symbols/Source/ModifierUtils.cs | 4 +- .../Symbols/Source/ParameterHelpers.cs | 2 +- .../Source/SourceComplexParameterSymbol.cs | 2 +- .../Symbols/Source/SourceMemberFieldSymbol.cs | 2 +- .../Symbols/Source/SourceNamedTypeSymbol.cs | 4 +- .../Source/SourceOrdinaryMethodSymbol.cs | 2 +- .../Source/SourcePropertyAccessorSymbol.cs | 2 +- .../Symbols/Source/SourcePropertySymbol.cs | 2 +- .../SourceUserDefinedConversionSymbol.cs | 2 +- .../Source/SourceUserDefinedOperatorSymbol.cs | 4 +- .../Portable/Syntax/SyntaxNodeExtensions.cs | 7 +- 27 files changed, 136 insertions(+), 80 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_AnonymousTypes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_AnonymousTypes.cs index d8373936fba69..94c4679db30e1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_AnonymousTypes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_AnonymousTypes.cs @@ -20,7 +20,7 @@ internal partial class Binder { private BoundExpression BindAnonymousObjectCreation(AnonymousObjectCreationExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureAnonymousTypes.CheckFeatureAvailability(diagnostics, node, node.NewKeyword.GetLocation()); + MessageID.IDS_FeatureAnonymousTypes.CheckFeatureAvailability(diagnostics, node.NewKeyword); // prepare var initializers = node.Initializers; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index e8f3afa1ee7bd..4dc2823f36c76 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -57,8 +57,7 @@ internal static void BindAttributeTypes( // Check the attribute type (unless the attribute type is already an error). if (boundTypeSymbol.TypeKind != TypeKind.Error) { - var location = attributeToBind.Name.GetLocation(); - binder.CheckDisallowedAttributeDependentType(boundType, location, diagnostics); + binder.CheckDisallowedAttributeDependentType(boundType, attributeToBind.Name, diagnostics); } boundAttributeTypes[i] = boundTypeSymbol; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs index 9f234db5e7507..bc4ae144fe345 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs @@ -18,7 +18,7 @@ internal partial class Binder { private BoundExpression BindAwait(AwaitExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureAsync.CheckFeatureAvailability(diagnostics, node, node.AwaitKeyword.GetLocation()); + MessageID.IDS_FeatureAsync.CheckFeatureAvailability(diagnostics, node.AwaitKeyword); BoundExpression expression = BindRValueWithoutTargetType(node.Expression, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index c4b5a5137b516..383223be84200 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -835,7 +835,7 @@ private BoundExpression BindScopedType(ExpressionSyntax node, BindingDiagnosticB private BoundExpression BindThrowExpression(ThrowExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureThrowExpression.CheckFeatureAvailability(diagnostics, node, node.ThrowKeyword.GetLocation()); + MessageID.IDS_FeatureThrowExpression.CheckFeatureAvailability(diagnostics, node.ThrowKeyword); bool hasErrors = node.HasErrors; if (!IsThrowExpressionInProperContext(node)) @@ -1358,29 +1358,29 @@ private BoundExpression BindTypeOf(TypeOfExpressionSyntax node, BindingDiagnosti } /// Called when an "attribute-dependent" type such as 'dynamic', 'string?', etc. is not permitted. - private void CheckDisallowedAttributeDependentType(TypeWithAnnotations typeArgument, Location errorLocation, BindingDiagnosticBag diagnostics) + private void CheckDisallowedAttributeDependentType(TypeWithAnnotations typeArgument, NameSyntax attributeName, BindingDiagnosticBag diagnostics) { typeArgument.VisitType(type: null, static (typeWithAnnotations, arg, _) => { - var (errorLocation, diagnostics) = arg; + var (attributeName, diagnostics) = arg; var type = typeWithAnnotations.Type; if (type.IsDynamic() || (typeWithAnnotations.NullableAnnotation.IsAnnotated() && !type.IsValueType) || type.IsNativeIntegerWrapperType || (type.IsTupleType && !type.TupleElementNames.IsDefault)) { - diagnostics.Add(ErrorCode.ERR_AttrDependentTypeNotAllowed, errorLocation, type); + diagnostics.Add(ErrorCode.ERR_AttrDependentTypeNotAllowed, attributeName, type); return true; } if (type.IsUnboundGenericType() || type.Kind == SymbolKind.TypeParameter) { - diagnostics.Add(ErrorCode.ERR_AttrTypeArgCannotBeTypeVar, errorLocation, type); + diagnostics.Add(ErrorCode.ERR_AttrTypeArgCannotBeTypeVar, attributeName, type); return true; } return false; - }, typePredicate: null, arg: (errorLocation, diagnostics)); + }, typePredicate: null, arg: (attributeName, diagnostics)); } private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, BindingDiagnosticBag diagnostics) @@ -1441,7 +1441,7 @@ internal static ConstantValue GetConstantSizeOf(TypeSymbol type) private BoundExpression BindDefaultExpression(DefaultExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureDefault.CheckFeatureAvailability(diagnostics, node, node.Keyword.GetLocation()); + MessageID.IDS_FeatureDefault.CheckFeatureAvailability(diagnostics, node.Keyword); TypeWithAnnotations typeWithAnnotations = this.BindType(node.Type, diagnostics, out AliasSymbol alias); var typeExpression = new BoundTypeExpression(node.Type, aliasOpt: alias, typeWithAnnotations); @@ -2901,12 +2901,12 @@ private void BindArgumentAndName( private BoundExpression BindArgumentValue(BindingDiagnosticBag diagnostics, ArgumentSyntax argumentSyntax, bool allowArglist, RefKind refKind) { if (argumentSyntax.RefKindKeyword.IsKind(SyntaxKind.InKeyword)) - MessageID.IDS_FeatureReadOnlyReferences.CheckFeatureAvailability(diagnostics, argumentSyntax, argumentSyntax.RefKindKeyword.GetLocation()); + MessageID.IDS_FeatureReadOnlyReferences.CheckFeatureAvailability(diagnostics, argumentSyntax.RefKindKeyword); if (argumentSyntax.Expression.Kind() == SyntaxKind.DeclarationExpression) { if (argumentSyntax.RefKindKeyword.IsKind(SyntaxKind.OutKeyword)) - MessageID.IDS_FeatureOutVar.CheckFeatureAvailability(diagnostics, argumentSyntax, argumentSyntax.RefKindKeyword.GetLocation()); + MessageID.IDS_FeatureOutVar.CheckFeatureAvailability(diagnostics, argumentSyntax.RefKindKeyword); var declarationExpression = (DeclarationExpressionSyntax)argumentSyntax.Expression; if (declarationExpression.IsOutDeclaration()) @@ -3436,7 +3436,7 @@ private BoundExpression BindArrayDimension(ExpressionSyntax dimension, BindingDi private BoundExpression BindImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node, BindingDiagnosticBag diagnostics) { // See BindArrayCreationExpression method above for implicitly typed array creation SPEC. - MessageID.IDS_FeatureImplicitArray.CheckFeatureAvailability(diagnostics, node, node.NewKeyword.GetLocation()); + MessageID.IDS_FeatureImplicitArray.CheckFeatureAvailability(diagnostics, node.NewKeyword); InitializerExpressionSyntax initializer = node.Initializer; int rank = node.Commas.Count + 1; @@ -3907,7 +3907,7 @@ private bool ReportBadStackAllocPosition(SyntaxNode node, BindingDiagnosticBag d inLegalPosition = (IsInMethodBody || IsLocalFunctionsScopeBinder) && node.IsLegalCSharp73SpanStackAllocPosition(); if (!inLegalPosition) { - MessageID.IDS_FeatureNestedStackalloc.CheckFeatureAvailability(diagnostics, node, node.GetFirstToken().GetLocation()); + MessageID.IDS_FeatureNestedStackalloc.CheckFeatureAvailability(diagnostics, node.GetFirstToken()); } } @@ -3989,7 +3989,7 @@ private BoundExpression BindStackAllocWithInitializer( { Debug.Assert(node.IsKind(SyntaxKind.ImplicitStackAllocArrayCreationExpression) || node.IsKind(SyntaxKind.StackAllocArrayCreationExpression)); - MessageID.IDS_FeatureStackAllocInitializer.CheckFeatureAvailability(diagnostics, node, stackAllocKeyword.GetLocation()); + MessageID.IDS_FeatureStackAllocInitializer.CheckFeatureAvailability(diagnostics, stackAllocKeyword); if (boundInitExprOpt.IsDefault) { @@ -4435,7 +4435,7 @@ constructor is not SynthesizedPrimaryConstructor && private BoundExpression BindImplicitObjectCreationExpression(ImplicitObjectCreationExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureImplicitObjectCreation.CheckFeatureAvailability(diagnostics, node, node.NewKeyword.GetLocation()); + MessageID.IDS_FeatureImplicitObjectCreation.CheckFeatureAvailability(diagnostics, node.NewKeyword); var arguments = AnalyzedArguments.GetInstance(); BindArgumentsAndNames(node.ArgumentList, diagnostics, arguments, allowArglist: true); @@ -4858,7 +4858,7 @@ private BoundObjectInitializerExpression BindObjectInitializerExpression( Debug.Assert((object)initializerType != null); if (initializerSyntax.Kind() == SyntaxKind.ObjectInitializerExpression) - MessageID.IDS_FeatureObjectInitializer.CheckFeatureAvailability(diagnostics, initializerSyntax, initializerSyntax.OpenBraceToken.GetLocation()); + MessageID.IDS_FeatureObjectInitializer.CheckFeatureAvailability(diagnostics, initializerSyntax.OpenBraceToken); // We use a location specific binder for binding object initializer field/property access to generate object initializer specific diagnostics: // 1) CS1914 (ERR_StaticMemberInObjectInitializer) @@ -5004,8 +5004,7 @@ private BoundExpression BindObjectInitializerMember( { var implicitIndexing = (ImplicitElementAccessSyntax)leftSyntax; - MessageID.IDS_FeatureDictionaryInitializer.CheckFeatureAvailability( - diagnostics, implicitIndexing, implicitIndexing.ArgumentList.OpenBracketToken.GetLocation()); + MessageID.IDS_FeatureDictionaryInitializer.CheckFeatureAvailability(diagnostics, implicitIndexing.ArgumentList.OpenBracketToken); boundMember = BindElementAccess(implicitIndexing, implicitReceiver, implicitIndexing.ArgumentList, diagnostics); @@ -5355,7 +5354,7 @@ private BoundCollectionInitializerExpression BindCollectionInitializerExpression Debug.Assert(initializerSyntax.Expressions.Any()); Debug.Assert((object)initializerType != null); - MessageID.IDS_FeatureCollectionInitializer.CheckFeatureAvailability(diagnostics, initializerSyntax, initializerSyntax.OpenBraceToken.GetLocation()); + MessageID.IDS_FeatureCollectionInitializer.CheckFeatureAvailability(diagnostics, initializerSyntax.OpenBraceToken); var initializerBuilder = ArrayBuilder.GetInstance(); @@ -6297,7 +6296,7 @@ private BoundLiteral BindLiteralConstant(LiteralExpressionSyntax node, BindingDi if (node.Token.Kind() is SyntaxKind.SingleLineRawStringLiteralToken or SyntaxKind.MultiLineRawStringLiteralToken) { - MessageID.IDS_FeatureRawStringLiterals.CheckFeatureAvailability(diagnostics, node, node.Location); + MessageID.IDS_FeatureRawStringLiterals.CheckFeatureAvailability(diagnostics, node); } return new BoundLiteral(node, cv, type); @@ -9366,7 +9365,7 @@ internal static bool ReportDelegateInvokeUseSiteDiagnostic(BindingDiagnosticBag private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccessExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureNullPropagatingOperator.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureNullPropagatingOperator.CheckFeatureAvailability(diagnostics, node.OperatorToken); BoundExpression receiver = BindConditionalAccessReceiver(node, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index 5f818e6af0cb7..ed1cbf9c8f28f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs @@ -56,7 +56,7 @@ private UnboundLambda AnalyzeAnonymousFunction( if (syntax is LambdaExpressionSyntax lambdaSyntax) { - MessageID.IDS_FeatureLambda.CheckFeatureAvailability(diagnostics, syntax, lambdaSyntax.ArrowToken.GetLocation()); + MessageID.IDS_FeatureLambda.CheckFeatureAvailability(diagnostics, lambdaSyntax.ArrowToken); checkAttributes(syntax, lambdaSyntax.AttributeLists, diagnostics); } @@ -86,7 +86,7 @@ private UnboundLambda AnalyzeAnonymousFunction( // delegate (int x) { } // delegate { } var anon = (AnonymousMethodExpressionSyntax)syntax; - MessageID.IDS_FeatureAnonDelegates.CheckFeatureAvailability(diagnostics, anon, anon.DelegateKeyword.GetLocation()); + MessageID.IDS_FeatureAnonDelegates.CheckFeatureAvailability(diagnostics, anon.DelegateKeyword); hasSignature = anon.ParameterList != null; if (hasSignature) @@ -105,12 +105,12 @@ private UnboundLambda AnalyzeAnonymousFunction( { if (modifier.IsKind(SyntaxKind.AsyncKeyword)) { - MessageID.IDS_FeatureAsync.CheckFeatureAvailability(diagnostics, syntax, modifier.GetLocation()); + MessageID.IDS_FeatureAsync.CheckFeatureAvailability(diagnostics, modifier); isAsync = true; } else if (modifier.IsKind(SyntaxKind.StaticKeyword)) { - MessageID.IDS_FeatureStaticAnonymousFunction.CheckFeatureAvailability(diagnostics, syntax, modifier.GetLocation()); + MessageID.IDS_FeatureStaticAnonymousFunction.CheckFeatureAvailability(diagnostics, modifier); isStatic = true; } } @@ -155,7 +155,7 @@ private UnboundLambda AnalyzeAnonymousFunction( } else { - MessageID.IDS_FeatureLambdaOptionalParameters.CheckFeatureAvailability(diagnostics, syntax, p.Default.EqualsToken.GetLocation()); + MessageID.IDS_FeatureLambdaOptionalParameters.CheckFeatureAvailability(diagnostics, p.Default.EqualsToken); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 1ac2909d7eec8..0653d993db96e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -2407,7 +2407,7 @@ private bool CheckConstraintLanguageVersionAndRuntimeSupportForOperator(SyntaxNo private BoundExpression BindSuppressNullableWarningExpression(PostfixUnaryExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureNullableReferenceTypes.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureNullableReferenceTypes.CheckFeatureAvailability(diagnostics, node.OperatorToken); var expr = BindExpression(node.Operand, diagnostics); switch (expr.Kind) @@ -4089,7 +4089,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node, private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken); BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment); ReportSuppressionIfNeeded(leftOperand, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 34a5f31066138..eae830cc46812 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -17,7 +17,7 @@ partial class Binder { private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node, node.IsKeyword.GetLocation()); + MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node.IsKeyword); BoundExpression expression = BindRValueWithoutTargetType(node.Expression, diagnostics); bool hasErrors = IsOperandErrors(node, ref expression, diagnostics); @@ -144,7 +144,7 @@ private BoundExpression BindSwitchExpression(SwitchExpressionSyntax node, Bindin { RoslynDebug.Assert(node is not null); - MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node, node.SwitchKeyword.GetLocation()); + MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node.SwitchKeyword); Binder? switchBinder = this.GetBinder(node); RoslynDebug.Assert(switchBinder is { }); @@ -194,7 +194,7 @@ private BoundPattern BindParenthesizedPattern( BindingDiagnosticBag diagnostics, bool underIsPattern) { - MessageID.IDS_FeatureParenthesizedPattern.CheckFeatureAvailability(diagnostics, node, node.OpenParenToken.GetLocation()); + MessageID.IDS_FeatureParenthesizedPattern.CheckFeatureAvailability(diagnostics, node.OpenParenToken); return BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern); } @@ -1088,7 +1088,7 @@ deconstructMethod is null && } else if (subPattern.ExpressionColon != null) { - MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, subPattern, subPattern.ExpressionColon.ColonToken.GetLocation()); + MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, subPattern.ExpressionColon.ColonToken); diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subPattern.ExpressionColon.Expression.Location); } @@ -1466,7 +1466,7 @@ private ImmutableArray BindPropertyPatternClause( foreach (SubpatternSyntax p in node.Subpatterns) { if (p.ExpressionColon is ExpressionColonSyntax) - MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, p, p.ExpressionColon.ColonToken.GetLocation()); + MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, p.ExpressionColon.ColonToken); ExpressionSyntax? expr = p.ExpressionColon?.Expression; PatternSyntax pattern = p.Pattern; @@ -1626,7 +1626,7 @@ private BoundPattern BindRelationalPattern( bool hasErrors, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureRelationalPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureRelationalPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken); BoundExpression value = BindExpressionForPattern(inputType, node.Expression, ref hasErrors, diagnostics, out var constantValueOpt, out _, out Conversion patternConversion); ExpressionSyntax innerExpression = SkipParensAndNullSuppressions(node.Expression, diagnostics, ref hasErrors); @@ -1719,7 +1719,7 @@ private BoundPattern BindUnaryPattern( BindingDiagnosticBag diagnostics, bool underIsPattern) { - MessageID.IDS_FeatureNotPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureNotPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken); bool permitDesignations = underIsPattern; // prevent designators under 'not' except under an is-pattern var subPattern = BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern); @@ -1736,7 +1736,7 @@ private BoundPattern BindBinaryPattern( bool isDisjunction = node.Kind() == SyntaxKind.OrPattern; if (isDisjunction) { - MessageID.IDS_FeatureOrPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureOrPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken); permitDesignations = false; // prevent designators under 'or' var left = BindPattern(node.Left, inputType, permitDesignations, hasErrors, diagnostics); @@ -1821,7 +1821,7 @@ static void collectCandidates(BoundPattern pat, ArrayBuilder candida } else { - MessageID.IDS_FeatureAndPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); + MessageID.IDS_FeatureAndPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken); var left = BindPattern(node.Left, inputType, permitDesignations, hasErrors, diagnostics); var right = BindPattern(node.Right, left.NarrowedType, permitDesignations, hasErrors, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 4a53b0d9ececd..1b4e98e15e444 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -23,7 +23,7 @@ internal partial class Binder internal BoundExpression BindQuery(QueryExpressionSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureQueryExpression.CheckFeatureAvailability(diagnostics, node, node.FromClause.FromKeyword.GetLocation()); + MessageID.IDS_FeatureQueryExpression.CheckFeatureAvailability(diagnostics, node.FromClause.FromKeyword); var fromClause = node.FromClause; var boundFromExpression = BindLeftOfPotentialColorColorMemberAccess(fromClause.Expression, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index cc10d881f3e13..116801cc47844 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -215,7 +215,7 @@ private BoundStatement BindFixedStatementParts(FixedStatementSyntax node, Bindin private void CheckRequiredLangVersionForIteratorMethods(YieldStatementSyntax statement, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureIterators.CheckFeatureAvailability(diagnostics, statement, statement.YieldKeyword.GetLocation()); + MessageID.IDS_FeatureIterators.CheckFeatureAvailability(diagnostics, statement.YieldKeyword); var method = (MethodSymbol)this.ContainingMemberOrLambda; if (method.IsAsync) @@ -550,7 +550,7 @@ private BoundStatement BindGoto(GotoStatementSyntax node, BindingDiagnosticBag d private BoundStatement BindLocalFunctionStatement(LocalFunctionStatementSyntax node, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureLocalFunctions.CheckFeatureAvailability(diagnostics, node, node.Identifier.GetLocation()); + MessageID.IDS_FeatureLocalFunctions.CheckFeatureAvailability(diagnostics, node.Identifier); // already defined symbol in containing block var localSymbol = this.LookupLocalFunction(node.Identifier); @@ -595,9 +595,9 @@ private BoundStatement BindLocalFunctionStatement(LocalFunctionStatementSyntax n foreach (var modifier in node.Modifiers) { if (modifier.IsKind(SyntaxKind.StaticKeyword)) - MessageID.IDS_FeatureStaticLocalFunctions.CheckFeatureAvailability(diagnostics, node, modifier.GetLocation()); + MessageID.IDS_FeatureStaticLocalFunctions.CheckFeatureAvailability(diagnostics, modifier); else if (modifier.IsKind(SyntaxKind.ExternKeyword)) - MessageID.IDS_FeatureExternLocalFunctions.CheckFeatureAvailability(diagnostics, node, modifier.GetLocation()); + MessageID.IDS_FeatureExternLocalFunctions.CheckFeatureAvailability(diagnostics, modifier); } return new BoundLocalFunctionStatement(node, localSymbol, blockBody, expressionBody, hasErrors); @@ -1426,7 +1426,7 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD var lhsKind = isRef ? BindValueKind.RefAssignable : BindValueKind.Assignable; if (isRef) - MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right, node.Right.GetFirstToken().GetLocation()); + MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right.GetFirstToken()); var op1 = BindValue(node.Left, diagnostics, lhsKind); ReportSuppressionIfNeeded(op1, diagnostics); @@ -3247,7 +3247,7 @@ private BoundCatchBlock BindCatchBlock(CatchClauseSyntax node, ArrayBuilder throw ExceptionUtilities.UnexpectedValue(expressionBody.Parent.Kind()), }; - messageId?.CheckFeatureAvailability(diagnostics, expressionBody, expressionBody.ArrowToken.GetLocation()); + messageId?.CheckFeatureAvailability(diagnostics, expressionBody.ArrowToken); Binder bodyBinder = this.GetBinder(expressionBody); Debug.Assert(bodyBinder != null); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index fb06a4e052467..8f668fc7684e4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -444,7 +444,7 @@ internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeOrAliasS case SyntaxKind.FunctionPointerType: var functionPointerTypeSyntax = (FunctionPointerTypeSyntax)syntax; - MessageID.IDS_FeatureFunctionPointers.CheckFeatureAvailability(diagnostics, syntax, functionPointerTypeSyntax.DelegateKeyword.GetLocation()); + MessageID.IDS_FeatureFunctionPointers.CheckFeatureAvailability(diagnostics, functionPointerTypeSyntax.DelegateKeyword); if (GetUnsafeDiagnosticInfo(sizeOfTypeOpt: null) is CSDiagnosticInfo info) { @@ -535,7 +535,7 @@ void reportNullableReferenceTypesIfNeeded(SyntaxToken questionToken, TypeWithAnn NamespaceOrTypeOrAliasSymbolWithAnnotations bindNullable() { var nullableSyntax = (NullableTypeSyntax)syntax; - MessageID.IDS_FeatureNullable.CheckFeatureAvailability(diagnostics, nullableSyntax, nullableSyntax.QuestionToken.GetLocation()); + MessageID.IDS_FeatureNullable.CheckFeatureAvailability(diagnostics, nullableSyntax.QuestionToken); TypeSyntax typeArgumentSyntax = nullableSyntax.ElementType; TypeWithAnnotations typeArgument = BindType(typeArgumentSyntax, diagnostics, basesBeingResolved); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs index 9bb42e8b61beb..1ff2935f76237 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs @@ -14,7 +14,7 @@ internal partial class Binder { private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingDiagnosticBag diagnostics) { - MessageID.IDS_FeatureRecords.CheckFeatureAvailability(diagnostics, syntax, syntax.WithKeyword.GetLocation()); + MessageID.IDS_FeatureRecords.CheckFeatureAvailability(diagnostics, syntax.WithKeyword); var receiver = BindRValueWithoutTargetType(syntax.Expression, diagnostics); var receiverType = receiver.Type; diff --git a/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs b/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs index b1aab32456052..6902fbbb5cbde 100644 --- a/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs +++ b/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs @@ -160,6 +160,9 @@ internal CSDiagnosticInfo Add(ErrorCode code, Location location) return info; } + internal CSDiagnosticInfo Add(ErrorCode code, SyntaxNodeOrToken syntax, params object[] args) + => Add(code, syntax.GetLocation()!, args); + internal CSDiagnosticInfo Add(ErrorCode code, Location location, params object[] args) { var info = new CSDiagnosticInfo(code, args); diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs index a70f367e3446b..3dcf824ff86db 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs @@ -267,7 +267,7 @@ private BoundSwitchLabel BindSwitchSectionLabel( { var matchLabelSyntax = (CasePatternSwitchLabelSyntax)node; - MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node, node.Keyword.GetLocation()); + MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node.Keyword); BoundPattern pattern = sectionBinder.BindPattern( matchLabelSyntax.Pattern, SwitchGoverningType, permitDesignations: true, node.HasErrors, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs index 8608c0caaafe5..3e3cb7f5c9ffa 100644 --- a/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Declarations/SingleTypeDeclaration.cs @@ -245,7 +245,7 @@ public bool Equals(TypeDeclarationIdentity other) return false; } - if ((object)thisDecl.Location.SourceTree != otherDecl.Location.SourceTree + if ((object)thisDecl.SyntaxReference.SyntaxTree != otherDecl.SyntaxReference.SyntaxTree && ((thisDecl.Modifiers & DeclarationModifiers.File) != 0 || (otherDecl.Modifiers & DeclarationModifiers.File) != 0)) { diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index e55e7ad573ffb..a080b65853dd6 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -314,31 +314,87 @@ public static LocalizableErrorArgument Localize(this MessageID id) } } + internal static bool CheckFeatureAvailability( + this MessageID feature, + DiagnosticBag diagnostics, + SyntaxNode syntax, + Location? location = null) + { + return CheckFeatureAvailability( + feature, + diagnostics, + syntax.SyntaxTree.Options, + static tuple => tuple.location ?? tuple.syntax.Location, + (syntax, location)); + } + + internal static bool CheckFeatureAvailability( + this MessageID feature, + DiagnosticBag diagnostics, + SyntaxToken syntax, + Location? location = null) + { + return CheckFeatureAvailability( + feature, + diagnostics, + syntax.SyntaxTree!.Options, + static tuple => tuple.location ?? tuple.syntax.GetLocation(), + (syntax, location)); + } + internal static bool CheckFeatureAvailability( this MessageID feature, BindingDiagnosticBag diagnostics, SyntaxNode syntax, Location? location = null) { - var diag = GetFeatureAvailabilityDiagnosticInfo(feature, (CSharpParseOptions)syntax.SyntaxTree.Options); - if (diag is object) + return CheckFeatureAvailability( + feature, + diagnostics, + syntax.SyntaxTree.Options, + static tuple => tuple.location ?? tuple.syntax.Location, + (syntax, location)); + } + + internal static bool CheckFeatureAvailability( + this MessageID feature, + BindingDiagnosticBag diagnostics, + SyntaxToken syntax, + Location? location = null) + { + return CheckFeatureAvailability( + feature, + diagnostics, + syntax.SyntaxTree!.Options, + static tuple => tuple.location ?? tuple.syntax.GetLocation(), + (syntax, location)); + } + + private static bool CheckFeatureAvailability( + this MessageID feature, + DiagnosticBag diagnostics, + ParseOptions parseOptions, + Func getLocation, + TData data) + { + if (GetFeatureAvailabilityDiagnosticInfo(feature, (CSharpParseOptions)parseOptions) is { } diagInfo) { - diagnostics.Add(diag, location ?? syntax.GetLocation()); + diagnostics.Add(diagInfo, getLocation(data)); return false; } return true; } - internal static bool CheckFeatureAvailability( + private static bool CheckFeatureAvailability( this MessageID feature, - DiagnosticBag diagnostics, - SyntaxNode syntax, - Location? location = null) + BindingDiagnosticBag diagnostics, + ParseOptions parseOptions, + Func getLocation, + TData data) { - var diag = GetFeatureAvailabilityDiagnosticInfo(feature, (CSharpParseOptions)syntax.SyntaxTree.Options); - if (diag is object) + if (GetFeatureAvailabilityDiagnosticInfo(feature, (CSharpParseOptions)parseOptions) is { } diagInfo) { - diagnostics.Add(diag, location ?? syntax.GetLocation()); + diagnostics.Add(diagInfo, getLocation(data)); return false; } return true; diff --git a/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs index 487971e6bbae6..f39280f32a662 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AliasSymbol.cs @@ -374,7 +374,7 @@ private NamespaceOrTypeSymbol ResolveAliasTarget( { if (usingDirective.UnsafeKeyword != default) { - MessageID.IDS_FeatureUsingTypeAlias.CheckFeatureAvailability(diagnostics, usingDirective, usingDirective.UnsafeKeyword.GetLocation()); + MessageID.IDS_FeatureUsingTypeAlias.CheckFeatureAvailability(diagnostics, usingDirective.UnsafeKeyword); } else if (usingDirective.NamespaceOrType is not NameSyntax) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs index 70a4c7370c399..efbe13b19594e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs @@ -27,7 +27,7 @@ internal static DeclarationModifiers MakeAndCheckNonTypeMemberModifiers( var readonlyToken = modifiers.FirstOrDefault(SyntaxKind.ReadOnlyKeyword); if (readonlyToken.Parent is MethodDeclarationSyntax or AccessorDeclarationSyntax or BasePropertyDeclarationSyntax or EventDeclarationSyntax) - modifierErrors |= !MessageID.IDS_FeatureReadOnlyMembers.CheckFeatureAvailability(diagnostics, readonlyToken.Parent, readonlyToken.GetLocation()); + modifierErrors |= !MessageID.IDS_FeatureReadOnlyMembers.CheckFeatureAvailability(diagnostics, readonlyToken); if ((result & DeclarationModifiers.AccessibilityMask) == 0) result |= defaultAccess; @@ -401,7 +401,7 @@ public static DeclarationModifiers ToDeclarationModifiers( if (one == DeclarationModifiers.Partial) { var messageId = isForTypeDeclaration ? MessageID.IDS_FeaturePartialTypes : MessageID.IDS_FeaturePartialMethod; - messageId.CheckFeatureAvailability(diagnostics, modifier.Parent, modifier.GetLocation()); + messageId.CheckFeatureAvailability(diagnostics, modifier); // `partial` must always be the last modifier according to the language. However, there was a bug // where we allowed `partial async` at the end of modifiers on methods. We keep this behavior for diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index 4c90e7e60b40e..613f0c478e7a9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -531,7 +531,7 @@ internal static void CheckParameterModifiers( if (parsingLambdaParams) { - MessageID.IDS_FeatureLambdaParamsArray.CheckFeatureAvailability(diagnostics, parameter, modifier.GetLocation()); + MessageID.IDS_FeatureLambdaParamsArray.CheckFeatureAvailability(diagnostics, modifier); } break; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 4544f4714dc9f..e8f76dd887aea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -367,7 +367,7 @@ private ConstantValue MakeDefaultExpression(BindingDiagnosticBag diagnostics, ou return ConstantValue.NotAvailable; } - MessageID.IDS_FeatureOptionalParameter.CheckFeatureAvailability(diagnostics, defaultSyntax, defaultSyntax.EqualsToken.GetLocation()); + MessageID.IDS_FeatureOptionalParameter.CheckFeatureAvailability(diagnostics, defaultSyntax.EqualsToken); binder = GetDefaultParameterValueBinder(defaultSyntax); Binder binderForDefault = binder.CreateBinderForParameterDefaultValue(this, defaultSyntax); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs index 54bf08a5356cb..c053192dea854 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs @@ -196,7 +196,7 @@ internal static DeclarationModifiers MakeModifiers(NamedTypeSymbol containingTyp foreach (var modifier in modifiers) { if (modifier.IsKind(SyntaxKind.FixedKeyword)) - MessageID.IDS_FeatureFixedBuffer.CheckFeatureAvailability(diagnostics, modifier.Parent, modifier.GetLocation()); + MessageID.IDS_FeatureFixedBuffer.CheckFeatureAvailability(diagnostics, modifier); } reportBadMemberFlagIfAny(result, DeclarationModifiers.Static, diagnostics, errorLocation); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index c861144f1cd16..01950c5021b6d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -181,7 +181,7 @@ private ImmutableArray MakeTypeParameters(BindingDiagnostic throw ExceptionUtilities.UnexpectedValue(typeDecl.Kind()); } - MessageID.IDS_FeatureGenerics.CheckFeatureAvailability(diagnostics, tpl, tpl.LessThanToken.GetLocation()); + MessageID.IDS_FeatureGenerics.CheckFeatureAvailability(diagnostics, tpl.LessThanToken); bool isInterfaceOrDelegate = typeKind == SyntaxKind.InterfaceDeclaration || typeKind == SyntaxKind.DelegateDeclaration; var parameterBuilder = new List(); @@ -197,7 +197,7 @@ private ImmutableArray MakeTypeParameters(BindingDiagnostic } else { - MessageID.IDS_FeatureTypeVariance.CheckFeatureAvailability(diagnostics, tp, tp.VarianceKeyword.GetLocation()); + MessageID.IDS_FeatureTypeVariance.CheckFeatureAvailability(diagnostics, tp.VarianceKeyword); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs index b107771ba450b..49ea558801938 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs @@ -533,7 +533,7 @@ private ImmutableArray MakeTypeParameters(MethodDeclaration { Debug.Assert(syntax.TypeParameterList != null); - MessageID.IDS_FeatureGenerics.CheckFeatureAvailability(diagnostics, syntax.TypeParameterList, syntax.TypeParameterList.LessThanToken.GetLocation()); + MessageID.IDS_FeatureGenerics.CheckFeatureAvailability(diagnostics, syntax.TypeParameterList.LessThanToken); OverriddenMethodTypeParameterMapBase typeMap = null; if (this.IsOverride) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index 9bf97d566b7ad..322b183327a77 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -235,7 +235,7 @@ protected SourcePropertyAccessorSymbol( } if (modifiers.Count > 0) - MessageID.IDS_FeaturePropertyAccessorMods.CheckFeatureAvailability(diagnostics, syntax, modifiers[0].GetLocation()); + MessageID.IDS_FeaturePropertyAccessorMods.CheckFeatureAvailability(diagnostics, modifiers[0]); } #nullable disable diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index a2247924b101e..af93d12c934ff 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -133,7 +133,7 @@ private SourcePropertySymbol( diagnostics); if (syntax is PropertyDeclarationSyntax { Initializer: { } initializer }) - MessageID.IDS_FeatureAutoPropertyInitializer.CheckFeatureAvailability(diagnostics, syntax, initializer.EqualsToken.GetLocation()); + MessageID.IDS_FeatureAutoPropertyInitializer.CheckFeatureAvailability(diagnostics, initializer.EqualsToken); } private TypeSyntax GetTypeSyntax(SyntaxNode syntax) => ((BasePropertyDeclarationSyntax)syntax).Type; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs index 136872cc5bb72..0e9bac249c720 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedConversionSymbol.cs @@ -27,7 +27,7 @@ public static SourceUserDefinedConversionSymbol CreateUserDefinedConversionSymbo if (name == WellKnownMemberNames.CheckedExplicitConversionName) { - MessageID.IDS_FeatureCheckedUserDefinedOperators.CheckFeatureAvailability(diagnostics, syntax, syntax.CheckedKeyword.GetLocation()); + MessageID.IDS_FeatureCheckedUserDefinedOperators.CheckFeatureAvailability(diagnostics, syntax.CheckedKeyword); } else if (syntax.CheckedKeyword.IsKind(SyntaxKind.CheckedKeyword)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs index b150708e7de10..4a9c89330c33f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbol.cs @@ -27,7 +27,7 @@ public static SourceUserDefinedOperatorSymbol CreateUserDefinedOperatorSymbol( if (SyntaxFacts.IsCheckedOperator(name)) { - MessageID.IDS_FeatureCheckedUserDefinedOperators.CheckFeatureAvailability(diagnostics, syntax, syntax.CheckedKeyword.GetLocation()); + MessageID.IDS_FeatureCheckedUserDefinedOperators.CheckFeatureAvailability(diagnostics, syntax.CheckedKeyword); } else if (!syntax.OperatorToken.IsMissing && syntax.CheckedKeyword.IsKind(SyntaxKind.CheckedKeyword)) { @@ -36,7 +36,7 @@ public static SourceUserDefinedOperatorSymbol CreateUserDefinedOperatorSymbol( if (name == WellKnownMemberNames.UnsignedRightShiftOperatorName) { - MessageID.IDS_FeatureUnsignedRightShift.CheckFeatureAvailability(diagnostics, syntax, syntax.OperatorToken.GetLocation()); + MessageID.IDS_FeatureUnsignedRightShift.CheckFeatureAvailability(diagnostics, syntax.OperatorToken); } var interfaceSpecifier = syntax.ExplicitInterfaceSpecifier; diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index c181404184893..f315f0a24792a 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -271,10 +271,10 @@ private static TypeSyntax SkipRefWorker(TypeSyntax syntax, BindingDiagnosticBag? (current.Parent is VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax } variableDeclaration && variableDeclaration.Type == current)); #endif - MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability(diagnostics, refType, refType.RefKeyword.GetLocation()); + MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability(diagnostics, refType.RefKeyword); if (refType.ReadOnlyKeyword != default) - MessageID.IDS_FeatureReadOnlyReferences.CheckFeatureAvailability(diagnostics, refType, refType.ReadOnlyKeyword.GetLocation()); + MessageID.IDS_FeatureReadOnlyReferences.CheckFeatureAvailability(diagnostics, refType.ReadOnlyKeyword); } return refType.Type; @@ -325,8 +325,7 @@ internal static SyntaxNode ModifyingScopedOrRefTypeOrSelf(this SyntaxNode syntax return syntax; } - MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability( - diagnostics, refExpression, refExpression.RefKeyword.GetLocation()); + MessageID.IDS_FeatureRefLocalsReturns.CheckFeatureAvailability(diagnostics, refExpression.RefKeyword); refKind = RefKind.Ref; expression.CheckDeconstructionCompatibleArgument(diagnostics); From 5ea9f2b6cf23c4e7bd0e055708518d3044aa78e8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 12 Apr 2023 23:49:09 -0700 Subject: [PATCH 45/69] Remove unused parameter --- .../AddImport/AbstractAddImportFeatureService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 6635ba547c850..88b1a04e5f7e5 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -169,7 +169,7 @@ private async Task> FindResultsAsync( // First search the current project to see if any symbols (source or metadata) match the // search string. await FindResultsInAllSymbolsInStartingProjectAsync( - allReferences, maxResults, finder, exact, cancellationToken).ConfigureAwait(false); + allReferences, finder, exact, cancellationToken).ConfigureAwait(false); // Only bother doing this for host workspaces. We don't want this for // things like the Interactive workspace as we can't even add project @@ -195,11 +195,11 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( - ConcurrentQueue allSymbolReferences, int maxResults, SymbolReferenceFinder finder, + ConcurrentQueue allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { var references = await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false); - AddRange(allSymbolReferences, references, maxResults); + AddRange(allSymbolReferences, references); } private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( @@ -325,7 +325,7 @@ private static async Task ProcessReferencesTask( if (finishedTask == task) { var result = await task.ConfigureAwait(false); - AddRange(allSymbolReferences, result, maxResults); + AddRange(allSymbolReferences, result); if (allSymbolReferences.Count >= maxResults) { @@ -441,7 +441,7 @@ private static HashSet GetViableUnreferencedProjects(Project project) return viableProjects; } - private static void AddRange(ConcurrentQueue allSymbolReferences, ImmutableArray proposedReferences, int maxResults) + private static void AddRange(ConcurrentQueue allSymbolReferences, ImmutableArray proposedReferences) where TReference : Reference { foreach (var reference in proposedReferences) From 077788bebbc05a5d7ddc2c3ba6a0f2ec3c10fc54 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 13 Apr 2023 13:08:58 +0200 Subject: [PATCH 46/69] Allow nested instance member access in `nameof` in static context (#67461) * Allow nested instance member access in 'nameof' in static context Co-authored-by: Yair Halberstadt * Make feature name more specific * Remove redundant check * Test more versions * Reuse expected diagnostics * Add symbol info tests * Add IOperation tests * Reuse existing helper * Add completion tests * Test lambdas and local functions * Test data flow --------- Co-authored-by: Yair Halberstadt --- .../Portable/Binder/Binder_Expressions.cs | 67 +- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/MessageID.cs | 3 + .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Test/Emit2/FlowAnalysis/FlowTests.cs | 26 + .../IOperationTests_INameOfOperation.cs | 203 ++++ .../Test/Semantic/Semantics/NameOfTests.cs | 892 +++++++++++++++++- .../StaticAbstractMembersInInterfacesTests.cs | 24 - .../CSharpCompletionCommandHandlerTests.vb | 60 ++ 21 files changed, 1299 insertions(+), 44 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 383223be84200..f62e0d476b2fd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -2108,19 +2108,18 @@ private BoundExpression SynthesizeReceiver(SyntaxNode node, Symbol member, Bindi (currentType.IsInterface && (declaringType.IsObjectType() || currentType.AllInterfacesNoUseSiteDiagnostics.Contains(declaringType)))) { bool hasErrors = false; - if (EnclosingNameofArgument != node) + if (!IsInsideNameof || (EnclosingNameofArgument != node && !node.IsFeatureEnabled(MessageID.IDS_FeatureInstanceMemberInNameof))) { + DiagnosticInfo diagnosticInfoOpt = null; if (InFieldInitializer && !currentType.IsScriptClass) { //can't access "this" in field initializers - Error(diagnostics, ErrorCode.ERR_FieldInitRefNonstatic, node, member); - hasErrors = true; + diagnosticInfoOpt = new CSDiagnosticInfo(ErrorCode.ERR_FieldInitRefNonstatic, member); } else if (InConstructorInitializer || InAttributeArgument) { //can't access "this" in constructor initializers or attribute arguments - Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member); - hasErrors = true; + diagnosticInfoOpt = new CSDiagnosticInfo(ErrorCode.ERR_ObjectRequired, member); } else { @@ -2132,12 +2131,24 @@ private BoundExpression SynthesizeReceiver(SyntaxNode node, Symbol member, Bindi if (!locationIsInstanceMember) { // error CS0120: An object reference is required for the non-static field, method, or property '{0}' - Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member); - hasErrors = true; + diagnosticInfoOpt = new CSDiagnosticInfo(ErrorCode.ERR_ObjectRequired, member); } } - hasErrors = hasErrors || IsRefOrOutThisParameterCaptured(node, diagnostics); + diagnosticInfoOpt ??= GetDiagnosticIfRefOrOutThisParameterCaptured(); + hasErrors = diagnosticInfoOpt is not null; + + if (hasErrors) + { + if (IsInsideNameof) + { + CheckFeatureAvailability(node, MessageID.IDS_FeatureInstanceMemberInNameof, diagnostics); + } + else + { + Error(diagnostics, diagnosticInfoOpt, node); + } + } } return ThisReference(node, currentType, hasErrors, wasCompilerGenerated: true); @@ -2309,20 +2320,35 @@ private BoundThisReference ThisReference(SyntaxNode node, NamedTypeSymbol thisTy return new BoundThisReference(node, thisTypeOpt ?? CreateErrorType(), hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; } +#nullable enable private bool IsRefOrOutThisParameterCaptured(SyntaxNodeOrToken thisOrBaseToken, BindingDiagnosticBag diagnostics) { - ParameterSymbol thisSymbol = this.ContainingMemberOrLambda.EnclosingThisSymbol(); - // If there is no this parameter, then it is definitely not captured and - // any diagnostic would be cascading. - if ((object)thisSymbol != null && thisSymbol.ContainingSymbol != ContainingMemberOrLambda && thisSymbol.RefKind != RefKind.None) + if (GetDiagnosticIfRefOrOutThisParameterCaptured() is { } diagnosticInfo) { - Error(diagnostics, ErrorCode.ERR_ThisStructNotInAnonMeth, thisOrBaseToken); + var location = thisOrBaseToken.GetLocation(); + Debug.Assert(location is not null); + Error(diagnostics, diagnosticInfo, location); return true; } return false; } + private DiagnosticInfo? GetDiagnosticIfRefOrOutThisParameterCaptured() + { + Debug.Assert(this.ContainingMemberOrLambda is not null); + ParameterSymbol? thisSymbol = this.ContainingMemberOrLambda.EnclosingThisSymbol(); + // If there is no this parameter, then it is definitely not captured and + // any diagnostic would be cascading. + if (thisSymbol is not null && thisSymbol.ContainingSymbol != ContainingMemberOrLambda && thisSymbol.RefKind != RefKind.None) + { + return new CSDiagnosticInfo(ErrorCode.ERR_ThisStructNotInAnonMeth); + } + + return null; + } +#nullable disable + private BoundBaseReference BindBase(BaseExpressionSyntax node, BindingDiagnosticBag diagnostics) { bool hasErrors = false; @@ -7746,10 +7772,17 @@ private bool CheckInstanceOrStatic( { if (instanceReceiver == true) { - ErrorCode errorCode = this.Flags.Includes(BinderFlags.ObjectInitializerMember) ? - ErrorCode.ERR_StaticMemberInObjectInitializer : - ErrorCode.ERR_ObjectProhibited; - Error(diagnostics, errorCode, node, symbol); + if (!IsInsideNameof) + { + ErrorCode errorCode = this.Flags.Includes(BinderFlags.ObjectInitializerMember) ? + ErrorCode.ERR_StaticMemberInObjectInitializer : + ErrorCode.ERR_ObjectProhibited; + Error(diagnostics, errorCode, node, symbol); + } + else if (CheckFeatureAvailability(node, MessageID.IDS_FeatureInstanceMemberInNameof, diagnostics)) + { + return false; + } resultKind = LookupResultKind.StaticInstanceMismatch; return true; } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 7b3d76e2f2759..d0db9d09556ab 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6814,6 +6814,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Parameter is unread. Did you forget to use it to initialize the property with that name? + + instance member in 'nameof' + The primary constructor conflicts with the synthesized copy constructor. diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index a080b65853dd6..92e08d6ae0c10 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -265,6 +265,8 @@ internal enum MessageID IDS_FeaturePrimaryConstructors = MessageBase + 12833, IDS_FeatureUsingTypeAlias = MessageBase + 12834, + + IDS_FeatureInstanceMemberInNameof = MessageBase + 12835, } // Message IDs may refer to strings that need to be localized. @@ -449,6 +451,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureLambdaParamsArray: // semantic check case MessageID.IDS_FeaturePrimaryConstructors: // declaration table check case MessageID.IDS_FeatureUsingTypeAlias: // semantic check + case MessageID.IDS_FeatureInstanceMemberInNameof: // semantic check return LanguageVersion.Preview; // C# 11.0 features. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 9eec4895c74af..cf2d5091ce7e3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1907,6 +1907,11 @@ odvozený typ delegáta + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes atributy lambda diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index f419ece90e2c2..d1aa50feea941 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1907,6 +1907,11 @@ abgeleiteter Delegattyp + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes Lambdaattribute diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 0434ca386a6b7..1d7eae3b86136 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1907,6 +1907,11 @@ tipo de delegado inferido + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes atributos de lambda diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 11a4c75e46504..d9515827f9d61 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1907,6 +1907,11 @@ type délégué déduit + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes attributs lambda diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index ca1540f042777..b9599ffc38273 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1907,6 +1907,11 @@ tipo di delegato dedotto + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes attributi lambda diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 561713d66a430..edc79dc35b41a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1907,6 +1907,11 @@ 推論されたデリゲート型 + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes ラムダ属性 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 128a8a2b7a1eb..8ba51bf19c702 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1907,6 +1907,11 @@ 유추된 대리자 형식 + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes 람다 특성 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c228fd971f016..d50d0d8303084 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1907,6 +1907,11 @@ Typ delegowania wywnioskowanego + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes atrybuty lambda diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 3c63e31f63721..6b2d4acb4bb83 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1907,6 +1907,11 @@ tipo representante inferido + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes lambda attributes diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index a82d18a9c3940..d1362cd391fc4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1907,6 +1907,11 @@ выводимый тип делегата + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes лямбда-атрибуты diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 6ce875379dc5b..63258ad292658 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1907,6 +1907,11 @@ çıkarsanan temsilci türü + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes lambda öznitelikleri diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index cbfe6c7f76eb0..80a8f77bb0f6b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1907,6 +1907,11 @@ 推断的委托类型 + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes Lambda 属性 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 15c8a3eb6db39..04a3ec570e8e7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1907,6 +1907,11 @@ 推斷委派類型 + + instance member in 'nameof' + instance member in 'nameof' + + lambda attributes Lambda 屬性 diff --git a/src/Compilers/CSharp/Test/Emit2/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Emit2/FlowAnalysis/FlowTests.cs index a6474e1d6859e..f718c080bade6 100644 --- a/src/Compilers/CSharp/Test/Emit2/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/FlowAnalysis/FlowTests.cs @@ -5912,5 +5912,31 @@ public static void Main() "; CreateCompilation(source).VerifyDiagnostics(); } + + [Fact] + public void NameOf_Nested() + { + var source = """ + System.Console.WriteLine(C.M()); + public class C + { + private C c; + public static string M() => nameof(c.c.c); + } + """; + + var expectedDiagnostic = + // (4,15): warning CS0649: Field 'C.c' is never assigned to, and will always have its default value null + // private C c; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "c").WithArguments("C.c", "null").WithLocation(4, 15); + + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "c").VerifyDiagnostics(expectedDiagnostic); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "c").VerifyDiagnostics(expectedDiagnostic); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + expectedDiagnostic, + // (5,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(c.c.c); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "c").WithArguments("instance member in 'nameof'").WithLocation(5, 40)); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INameOfOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INameOfOperation.cs index 2587442a62a18..4f7975c981bb2 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INameOfOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INameOfOperation.cs @@ -223,5 +223,208 @@ void M() "; VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact] + public void NameOfFlow_InstanceMemberFromStatic_Flat() + { + var source = """ + public class C + { + public int Property { get; } + public int Field; + public event System.Action Event; + + public static string StaticMethod() + /**/{ + return nameof(Property) + + nameof(Field) + + nameof(Event); + }/**/ + } + """; + + var expectedFlowGraph = """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyFieldEvent") (Syntax: 'nameof(Prop ... meof(Event)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyField") (Syntax: 'nameof(Prop ... meof(Field)') + Left: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Property") (Syntax: 'nameof(Property)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Field") (Syntax: 'nameof(Field)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Event") (Syntax: 'nameof(Event)') + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, DiagnosticDescription.None); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact] + public void NameOfFlow_InstanceMemberFromStatic_Flat_MethodGroup() + { + var source = """ + public class C + { + public void Method1() { } + public void Method1(int i) { } + public void Method2() { } + public static void Method2(int i) { } + + public static string StaticMethod() + /**/{ + return nameof(Method1) + + nameof(Method2); + }/**/ + } + """; + + var expectedFlowGraph = """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "Method1Method2") (Syntax: 'nameof(Meth ... of(Method2)') + Left: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Method1") (Syntax: 'nameof(Method1)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Method2") (Syntax: 'nameof(Method2)') + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, DiagnosticDescription.None); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67565")] + public void NameOfFlow_InstanceMemberFromStatic_Nested() + { + var source = """ + public class C + { + public C1 Property { get; } + public C1 Field; + + public static string StaticMethod() + /**/{ + return nameof(Property.Property) + + nameof(Property.Field) + + nameof(Property.Event) + + nameof(Field.Property) + + nameof(Field.Field) + + nameof(Field.Event); + }/**/ + } + + public class C1 + { + public int Property { get; } + public int Field; + public event System.Action Event; + } + """; + + var expectedFlowGraph = """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyFieldEventPropertyFieldEvent") (Syntax: 'nameof(Prop ... ield.Event)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyFieldEventPropertyField") (Syntax: 'nameof(Prop ... ield.Field)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyFieldEventProperty") (Syntax: 'nameof(Prop ... d.Property)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyFieldEvent") (Syntax: 'nameof(Prop ... erty.Event)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "PropertyField") (Syntax: 'nameof(Prop ... erty.Field)') + Left: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Property") (Syntax: 'nameof(Prop ... y.Property)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Field") (Syntax: 'nameof(Property.Field)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Event") (Syntax: 'nameof(Property.Event)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Property") (Syntax: 'nameof(Field.Property)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Field") (Syntax: 'nameof(Field.Field)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Event") (Syntax: 'nameof(Field.Event)') + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, DiagnosticDescription.None); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67565")] + public void NameOfFlow_InstanceMemberFromStatic_Nested_MethodGroup() + { + var source = """ + public class C + { + public C1 Property { get; } + public C1 Field; + public event System.Action Event; + + public static string StaticMethod() + /**/{ + return nameof(Property.Method) + + nameof(Field.Method) + + nameof(Event.Invoke); + }/**/ + } + + public class C1 + { + public void Method() { } + public void Method(int i) { } + } + """; + + var expectedFlowGraph = """ + Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Next (Return) Block[B2] + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "MethodMethodInvoke") (Syntax: 'nameof(Prop ... ent.Invoke)') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String, Constant: "MethodMethod") (Syntax: 'nameof(Prop ... eld.Method)') + Left: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Method") (Syntax: 'nameof(Property.Method)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Method") (Syntax: 'nameof(Field.Method)') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "Invoke") (Syntax: 'nameof(Event.Invoke)') + Block[B2] - Exit + Predecessors: [B1] + Statements (0) + """; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, DiagnosticDescription.None); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs index 4699165bab2b7..98a9d1f8137a9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NameOfTests.cs @@ -4,13 +4,15 @@ #nullable disable -using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; -using System.Threading; -using System.Linq; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { @@ -954,6 +956,315 @@ private static void Use(object o) {} Assert.Equal(0, symbolInfo.CandidateSymbols.Length); } + [Fact] + public void SymbolInfo_InstanceMemberFromStatic_Flat() + { + var source = """ + public class C + { + public int Property { get; } + public int Field; + public event System.Action Event; + + public static string StaticField = + nameof(Property) + + nameof(Field) + + nameof(Event); + } + """; + var comp = CreateCompilation(source).VerifyDiagnostics(); + + var cProperty = comp.GetMember("C.Property"); + var cField = comp.GetMember("C.Field"); + var cEvent = comp.GetMember("C.Event"); + + var tree = comp.SyntaxTrees.Single(); + var tree2 = SyntaxFactory.ParseSyntaxTree(source + " "); + + var initializer = tree2.GetRoot().DescendantNodes().OfType().Single(); + + var nameofCalls = getNameOfCalls(tree); + Assert.Equal(3, nameofCalls.Length); + var nameofCalls2 = getNameOfCalls(tree2); + Assert.Equal(3, nameofCalls2.Length); + + var model = comp.GetSemanticModel(tree); + + verify(0, "Property", cProperty); + verify(1, "Field", cField); + verify(2, "Event", cEvent); + + void verify(int index, string expression, Symbol symbol) + { + var argument = nameofCalls[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument.ToString()); + + verifySymbolInfo(model.GetSymbolInfo(argument)); + + var argument2 = nameofCalls2[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument2.ToString()); + + Assert.True(model.TryGetSpeculativeSemanticModel(initializer.Position, initializer, out var model2)); + + verifySymbolInfo(model2.GetSymbolInfo(argument2)); + + verifySymbolInfo(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsExpression)); + + Assert.True(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsTypeOrNamespace).IsEmpty); + + void verifySymbolInfo(SymbolInfo symbolInfo) + { + Assert.NotNull(symbolInfo.Symbol); + Assert.Same(symbol.GetPublicSymbol(), symbolInfo.Symbol); + } + } + + static ImmutableArray getNameOfCalls(SyntaxTree tree) + { + return tree.GetRoot().DescendantNodes().OfType() + .Where(e => e.Expression is IdentifierNameSyntax { Identifier.ValueText: "nameof" }) + .ToImmutableArray(); + } + } + + [Fact] + public void SymbolInfo_InstanceMemberFromStatic_Flat_MethodGroup() + { + var source = """ + public class C + { + public void Method1() { } + public void Method1(int i) { } + public void Method2() { } + public static void Method2(int i) { } + + public static string StaticField = + nameof(Method1) + + nameof(Method2); + } + """; + var comp = CreateCompilation(source).VerifyDiagnostics(); + + var cMethods1 = comp.GetMembers("C.Method1"); + Assert.Equal(2, cMethods1.Length); + var cMethods2 = comp.GetMembers("C.Method2"); + Assert.Equal(2, cMethods2.Length); + + var tree = comp.SyntaxTrees.Single(); + var tree2 = SyntaxFactory.ParseSyntaxTree(source + " "); + + var initializer = tree2.GetRoot().DescendantNodes().OfType().Single(); + + var nameofCalls = getNameOfCalls(tree); + Assert.Equal(2, nameofCalls.Length); + var nameofCalls2 = getNameOfCalls(tree2); + Assert.Equal(2, nameofCalls2.Length); + + var model = comp.GetSemanticModel(tree); + + verify(0, "Method1", cMethods1); + verify(1, "Method2", cMethods2); + + void verify(int index, string expression, ImmutableArray symbols) + { + var argument = nameofCalls[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument.ToString()); + + verifySymbolInfo(CandidateReason.MemberGroup, model.GetSymbolInfo(argument)); + + var argument2 = nameofCalls2[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument2.ToString()); + + Assert.True(model.TryGetSpeculativeSemanticModel(initializer.Position, initializer, out var model2)); + + verifySymbolInfo(CandidateReason.MemberGroup, model2.GetSymbolInfo(argument2)); + + verifySymbolInfo(CandidateReason.OverloadResolutionFailure, model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsExpression)); + + Assert.True(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsTypeOrNamespace).IsEmpty); + + void verifySymbolInfo(CandidateReason reason, SymbolInfo symbolInfo) + { + Assert.Equal(reason, symbolInfo.CandidateReason); + AssertEx.SetEqual( + symbols.Select(s => s.GetPublicSymbol()), + symbolInfo.CandidateSymbols, + Roslyn.Utilities.ReferenceEqualityComparer.Instance); + } + } + + static ImmutableArray getNameOfCalls(SyntaxTree tree) + { + return tree.GetRoot().DescendantNodes().OfType() + .Where(e => e.Expression is IdentifierNameSyntax { Identifier.ValueText: "nameof" }) + .ToImmutableArray(); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67565")] + public void SymbolInfo_InstanceMemberFromStatic_Nested() + { + var source = """ + public class C + { + public C1 Property { get; } + public C1 Field; + + public static string StaticField = + nameof(Property.Property) + + nameof(Property.Field) + + nameof(Property.Event) + + nameof(Field.Property) + + nameof(Field.Field) + + nameof(Field.Event); + } + + public class C1 + { + public int Property { get; } + public int Field; + public event System.Action Event; + } + """; + var comp = CreateCompilation(source).VerifyDiagnostics(); + + var c1Property = comp.GetMember("C1.Property"); + var c1Field = comp.GetMember("C1.Field"); + var c1Event = comp.GetMember("C1.Event"); + + var tree = comp.SyntaxTrees.Single(); + var tree2 = SyntaxFactory.ParseSyntaxTree(source + " "); + + var initializer = tree2.GetRoot().DescendantNodes().OfType().Single(); + + var nameofCalls = getNameOfCalls(tree); + Assert.Equal(6, nameofCalls.Length); + var nameofCalls2 = getNameOfCalls(tree2); + Assert.Equal(6, nameofCalls2.Length); + + var model = comp.GetSemanticModel(tree); + + verify(0, "Property.Property", c1Property); + verify(1, "Property.Field", c1Field); + verify(2, "Property.Event", c1Event); + verify(3, "Field.Property", c1Property); + verify(4, "Field.Field", c1Field); + verify(5, "Field.Event", c1Event); + + void verify(int index, string expression, Symbol symbol) + { + var argument = nameofCalls[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument.ToString()); + + verifySymbolInfo(model.GetSymbolInfo(argument)); + + var argument2 = nameofCalls2[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument2.ToString()); + + Assert.True(model.TryGetSpeculativeSemanticModel(initializer.Position, initializer, out var model2)); + + verifySymbolInfo(model2.GetSymbolInfo(argument2)); + + verifySymbolInfo(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsExpression)); + + Assert.True(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsTypeOrNamespace).IsEmpty); + + void verifySymbolInfo(SymbolInfo symbolInfo) + { + Assert.NotNull(symbolInfo.Symbol); + Assert.Same(symbol.GetPublicSymbol(), symbolInfo.Symbol); + } + } + + static ImmutableArray getNameOfCalls(SyntaxTree tree) + { + return tree.GetRoot().DescendantNodes().OfType() + .Where(e => e.Expression is IdentifierNameSyntax { Identifier.ValueText: "nameof" }) + .ToImmutableArray(); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67565")] + public void SymbolInfo_InstanceMemberFromStatic_Nested_MethodGroup() + { + var source = """ + public class C + { + public C1 Property { get; } + public C1 Field; + public event System.Action Event; + + public static string StaticField = + nameof(Property.Method) + + nameof(Field.Method) + + nameof(Event.Invoke); + } + + public class C1 + { + public void Method() { } + public void Method(int i) { } + } + """; + var comp = CreateCompilation(source).VerifyDiagnostics(); + + var c1Methods = comp.GetMembers("C1.Method").ToArray(); + Assert.Equal(2, c1Methods.Length); + var c1Event = comp.GetMember("C1.Event"); + var actionInvoke = comp.GetWellKnownType(WellKnownType.System_Action).GetMember("Invoke"); + + var tree = comp.SyntaxTrees.Single(); + var tree2 = SyntaxFactory.ParseSyntaxTree(source + " "); + + var initializer = tree2.GetRoot().DescendantNodes().OfType().Single(); + + var nameofCalls = getNameOfCalls(tree); + Assert.Equal(3, nameofCalls.Length); + var nameofCalls2 = getNameOfCalls(tree2); + Assert.Equal(3, nameofCalls2.Length); + + var model = comp.GetSemanticModel(tree); + + verify(0, "Property.Method", c1Methods); + verify(1, "Field.Method", c1Methods); + verify(2, "Event.Invoke", actionInvoke); + + void verify(int index, string expression, params Symbol[] symbols) + { + var argument = nameofCalls[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument.ToString()); + + verifySymbolInfo(CandidateReason.MemberGroup, model.GetSymbolInfo(argument)); + + var argument2 = nameofCalls2[index].ArgumentList.Arguments.Single().Expression; + Assert.Equal(expression, argument2.ToString()); + + Assert.True(model.TryGetSpeculativeSemanticModel(initializer.Position, initializer, out var model2)); + + verifySymbolInfo(CandidateReason.MemberGroup, model2.GetSymbolInfo(argument2)); + + verifySymbolInfo(CandidateReason.OverloadResolutionFailure, model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsExpression)); + + Assert.True(model.GetSpeculativeSymbolInfo(argument2.Position, argument2, SpeculativeBindingOption.BindAsTypeOrNamespace).IsEmpty); + + void verifySymbolInfo(CandidateReason reason, SymbolInfo symbolInfo) + { + Assert.Equal(reason, symbolInfo.CandidateReason); + AssertEx.SetEqual( + symbols.Select(s => s.GetPublicSymbol()), + symbolInfo.CandidateSymbols, + Roslyn.Utilities.ReferenceEqualityComparer.Instance); + } + } + + static ImmutableArray getNameOfCalls(SyntaxTree tree) + { + return tree.GetRoot().DescendantNodes().OfType() + .Where(e => e.Expression is IdentifierNameSyntax { Identifier.ValueText: "nameof" }) + .ToImmutableArray(); + } + } + [Fact] public void ExtensionMethodConstraintFailed() { @@ -1487,5 +1798,580 @@ public void nameof(string x) var option = TestOptions.ReleaseDll; CreateCompilation(source, options: option).VerifyDiagnostics(); } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceInstanceMembersFromStaticMemberInNameof_Flat() + { + var source = @" +System.Console.Write(C.M()); +public class C +{ + public object Property { get; } + public object Field; + public event System.Action Event; + public void M2() { } + public static string M() => nameof(Property) + + "","" + nameof(Field) + + "","" + nameof(Event) + + "","" + nameof(M2) + ; +}"; + var expectedOutput = "Property,Field,Event,M2"; + + CompileAndVerify(source, parseOptions: TestOptions.Regular11, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceInstanceMembersFromStaticMemberInNameof_Nested() + { + var source = @" +System.Console.Write(C.M()); +public class C +{ + public C1 Property { get; } + public C1 Field; + public event System.Action Event; + public static string M() => nameof(Property.Property) + + "","" + nameof(Property.Field) + + "","" + nameof(Property.Method) + + "","" + nameof(Property.Event) + + "","" + nameof(Field.Property) + + "","" + nameof(Field.Field) + + "","" + nameof(Field.Method) + + "","" + nameof(Field.Event) + + "","" + nameof(Event.Invoke) + ; +} + +public class C1 +{ + public int Property { get; } + public int Field; + public void Method(){} + public event System.Action Event; +}"; + var expectedOutput = "Property,Field,Method,Event,Property,Field,Method,Event,Invoke"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput).VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (8,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(Property.Property) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Property").WithArguments("instance member in 'nameof'").WithLocation(8, 40), + // (9,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Property.Field) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Property").WithArguments("instance member in 'nameof'").WithLocation(9, 24), + // (10,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Property.Method) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Property").WithArguments("instance member in 'nameof'").WithLocation(10, 24), + // (11,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Property.Event) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Property").WithArguments("instance member in 'nameof'").WithLocation(11, 24), + // (12,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Field.Property) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Field").WithArguments("instance member in 'nameof'").WithLocation(12, 24), + // (13,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Field.Field) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Field").WithArguments("instance member in 'nameof'").WithLocation(13, 24), + // (14,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Field.Method) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Field").WithArguments("instance member in 'nameof'").WithLocation(14, 24), + // (15,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Field.Event) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Field").WithArguments("instance member in 'nameof'").WithLocation(15, 24), + // (16,24): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // + "," + nameof(Event.Invoke) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Event").WithArguments("instance member in 'nameof'").WithLocation(16, 24)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void InstanceFromStatic_Lambdas() + { + var source = """ + using System; + Console.Write(C.Names()); + public class C + { + public object Property { get; } + public object Field; + public event Action Event; + public void Method() { } + public static string Names() + { + var lambda1 = static () => nameof(Property); + var lambda2 = static (string f = nameof(Field)) => f; + var lambda3 = static () => nameof(Event.Invoke); + var lambda4 = static (string i = nameof(Event.Invoke)) => i; + return lambda1() + "," + lambda2() + "," + lambda3() + "," + lambda4(); + } + } + """; + var expectedOutput = "Property,Field,Invoke,Invoke"; + + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput).VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (12,40): error CS8652: The feature 'lambda optional parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var lambda2 = static (string f = nameof(Field)) => f; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "=").WithArguments("lambda optional parameters").WithLocation(12, 40), + // (13,43): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var lambda3 = static () => nameof(Event.Invoke); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Event").WithArguments("instance member in 'nameof'").WithLocation(13, 43), + // (14,40): error CS8652: The feature 'lambda optional parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var lambda4 = static (string i = nameof(Event.Invoke)) => i; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "=").WithArguments("lambda optional parameters").WithLocation(14, 40), + // (14,49): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var lambda4 = static (string i = nameof(Event.Invoke)) => i; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Event").WithArguments("instance member in 'nameof'").WithLocation(14, 49)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void InstanceFromStatic_LocalFunctions() + { + var source = """ + using System; + Console.Write(C.Names()); + public class C + { + public object Property { get; } + public object Field; + public event Action Event; + public void Method() { } + public static string Names() + { + static string local1() => nameof(Property); + static string local2(string f = nameof(Field)) => f; + static string local3() => nameof(Event.Invoke); + static string local4(string i = nameof(Event.Invoke)) => i; + return local1() + "," + local2() + "," + local3() + "," + local4(); + } + } + """; + var expectedOutput = "Property,Field,Invoke,Invoke"; + + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput).VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (13,42): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static string local3() => nameof(Event.Invoke); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Event").WithArguments("instance member in 'nameof'").WithLocation(13, 42), + // (14,48): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static string local4(string i = nameof(Event.Invoke)) => i; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Event").WithArguments("instance member in 'nameof'").WithLocation(14, 48)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceInstanceMembersFromFieldInitializerInNameof() + { + var source = @" +System.Console.Write(new C().S); +public class C +{ + public string S { get; } = nameof(S.Length); +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "Length").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Length").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (5,39): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public string S { get; } = nameof(S.Length); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(5, 39)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceInstanceMembersFromAttributeInNameof() + { + var source = @" +var p = new C().P; // 1 +public class C +{ + [System.Obsolete(nameof(S.Length))] + public int P { get; } + public string S { get; } +}"; + var expectedDiagnostics = new[] + { + // (2,9): warning CS0618: 'C.P' is obsolete: 'Length' + // var p = new C().P; // 1 + Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "new C().P").WithArguments("C.P", "Length").WithLocation(2, 9) + }; + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (5,29): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [System.Obsolete(nameof(S.Length))] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(5, 29)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceInstanceMembersFromConstructorInitializersInNameof() + { + var source = @" +System.Console.WriteLine(new C().S); +public class C +{ + public C(string s){ S = s; } + public C() : this(nameof(S.Length)){} + public string S { get; } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "Length").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Length").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (6,30): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public C() : this(nameof(S.Length)){} + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(6, 30)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanAccessStructInstancePropertyInLambdaInNameof() + { + var source = @" +using System; + +string s = ""str""; +new S().M(ref s); + +public struct S +{ + public string P { get; } + public void M(ref string x) + { + Func func = () => nameof(P.Length); + Console.WriteLine(func()); + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "Length").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Length").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (12,42): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // Func func = () => nameof(P.Length); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P").WithArguments("instance member in 'nameof'").WithLocation(12, 42)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameof1() + { + var source = @" +System.Console.WriteLine(new C().M()); +public class C +{ + public C Prop { get; } + public static int StaticProp { get; } + public string M() => nameof(Prop.StaticProp); +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "StaticProp").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "StaticProp").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (7,33): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public string M() => nameof(Prop.StaticProp); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop.StaticProp").WithArguments("instance member in 'nameof'").WithLocation(7, 33)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameof2() + { + var source = @" +System.Console.WriteLine(C.M()); +public class C +{ + public C Prop { get; } + public static int StaticProp { get; } + public static string M() => nameof(Prop.StaticProp); +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "StaticProp").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "StaticProp").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (7,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(Prop.StaticProp); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop").WithArguments("instance member in 'nameof'").WithLocation(7, 40), + // (7,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(Prop.StaticProp); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop.StaticProp").WithArguments("instance member in 'nameof'").WithLocation(7, 40)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameof3() + { + var source = @" +System.Console.WriteLine(C.M()); +public class C +{ + public C Prop { get; } + public static string M() => nameof(Prop.M); +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "M").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "M").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (6,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(Prop.M); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop").WithArguments("instance member in 'nameof'").WithLocation(6, 40)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameof4() + { + var source = @" +System.Console.WriteLine(new C().M()); +public class C +{ + public C Prop { get; } + public static void StaticMethod(){} + public string M() => nameof(Prop.StaticMethod); +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "StaticMethod").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "StaticMethod").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics(); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotReferenceInstanceMembersFromStaticMemberInNameofInCSharp11() + { + var source = @" +public class C +{ + public string S { get; } + public static string M() => nameof(S.Length); +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (5,40): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static string M() => nameof(S.Length); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(5, 40)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotReferenceInstanceMembersFromFieldInitializerInNameofInCSharp11() + { + var source = @" +public class C +{ + public string S { get; } = nameof(S.Length); +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (4,39): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public string S { get; } = nameof(S.Length); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(4, 39)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotReferenceInstanceMembersFromAttributeInNameofInCSharp11() + { + var source = @" +public class C +{ + [System.Obsolete(nameof(S.Length))] + public int P { get; } + public string S { get; } +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (4,29): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [System.Obsolete(nameof(S.Length))] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(4, 29)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotReferenceInstanceMembersFromConstructorInitializersInNameofInCSharp11() + { + var source = @" +public class C +{ + public C(string s){} + public C() : this(nameof(S.Length)){} + public string S { get; } +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (5,30): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public C() : this(nameof(S.Length)){} + Diagnostic(ErrorCode.ERR_FeatureInPreview, "S").WithArguments("instance member in 'nameof'").WithLocation(5, 30)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotAccessStructInstancePropertyInLambdaInNameofInCSharp11() + { + var source = @" +using System; + +public struct S +{ + public string P { get; } + public void M(ref string x) + { + Func func = () => nameof(P.Length); + } +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (9,42): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // Func func = () => nameof(P.Length); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P").WithArguments("instance member in 'nameof'").WithLocation(9, 42)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCannotReferenceStaticPropertyFromInstanceMemberInNameofInCSharp11() + { + var source = @" +public class C +{ + public C Prop { get; } + public static int StaticProp { get; } + public string M() => nameof(Prop.StaticProp); +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (6,33): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public string M() => nameof(Prop.StaticProp); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop.StaticProp").WithArguments("instance member in 'nameof'").WithLocation(6, 33)); + } + + [Fact] + public void TestCanReferenceStaticMethodFromInstanceMemberInNameofInCSharp11() + { + var source = @" +System.Console.WriteLine(new C().M()); +public class C +{ + public C Prop { get; } + public static void StaticMethod(){} + public string M() => nameof(Prop.StaticMethod); +}"; + CompileAndVerify(source, parseOptions: TestOptions.Regular11, expectedOutput: "StaticMethod").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "StaticMethod").VerifyDiagnostics(); + } + + [Fact] + public void TestCanAccessRefParameterInLambdaInNameof() + { + var source = @" +using System; + +string s = ""str""; +new S().M(ref s); + +public struct S +{ + public void M(ref string x) + { + Func func = () => nameof(x.Length); + Console.WriteLine(func()); + } +}"; + CompileAndVerify(source, parseOptions: TestOptions.Regular11, expectedOutput: "Length").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Length").VerifyDiagnostics(); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameofUsedRecursivelyInAttributes1() + { + var source = @" +using System; +using System.Reflection; +Console.WriteLine(typeof(C).GetProperty(""Prop"").GetCustomAttribute().S); +class C +{ + [Attr(nameof(Prop.StaticMethod))] + public C Prop { get; } + public static void StaticMethod(){} +} +class Attr : Attribute +{ + public readonly string S; + public Attr(string s) { S = s; } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "StaticMethod").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "StaticMethod").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (7,18): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [Attr(nameof(Prop.StaticMethod))] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop").WithArguments("instance member in 'nameof'").WithLocation(7, 18)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameofUsedRecursivelyInAttributes2() + { + var source = @" +using System; +using System.Reflection; +Console.WriteLine(typeof(C).GetProperty(""Prop"").GetCustomAttribute().S); +class C +{ + [Attr(nameof(Prop.Prop))] + public static C Prop { get; } +} +class Attr : Attribute +{ + public readonly string S; + public Attr(string s) { S = s; } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "Prop").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Prop").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (7,18): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [Attr(nameof(Prop.Prop))] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop.Prop").WithArguments("instance member in 'nameof'").WithLocation(7, 18)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestCanReferenceStaticMembersFromInstanceMemberInNameofUsedRecursivelyInAttributes3() + { + var source = @" +using System; +using System.Reflection; +Console.WriteLine(typeof(C).GetCustomAttribute().S); +[Attr(nameof(C.Prop.Prop))] +class C +{ + public static C Prop { get; } +} +class Attr : Attribute +{ + public readonly string S; + public Attr(string s) { S = s; } +}"; + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, expectedOutput: "Prop").VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: "Prop").VerifyDiagnostics(); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (5,14): error CS8652: The feature 'instance member in 'nameof'' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // [Attr(nameof(C.Prop.Prop))] + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C.Prop.Prop").WithArguments("instance member in 'nameof'").WithLocation(5, 14)); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestInvalidRecursiveUsageOfNameofInAttributesDoesNotCrashCompiler1() + { + var source = @" +class C +{ + [Attr(nameof(Method().Method))] + T Method() where T : C => default; +} +class Attr : System.Attribute { public Attr(string s) {} }"; + var expectedDiagnostics = new[] + { + // (4,18): error CS0411: The type arguments for method 'C.Method()' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // [Attr(nameof(Method().Method))] + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Method").WithArguments("C.Method()").WithLocation(4, 18) + }; + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact, WorkItem(40229, "https://github.com/dotnet/roslyn/issues/40229")] + public void TestInvalidRecursiveUsageOfNameofInAttributesDoesNotCrashCompiler2() + { + var source = @" +class C +{ + [Attr(nameof(Method().Method))] + T Method() where T : C => default; +} +class Attr : System.Attribute { public Attr(string s) {} }"; + var expectedDiagnostics = new[] + { + // (4,18): error CS8082: Sub-expression cannot be used in an argument to nameof. + // [Attr(nameof(Method().Method))] + Diagnostic(ErrorCode.ERR_SubexpressionNotInNameof, "Method()").WithLocation(4, 18) + }; + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics(expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs index f35bd414f7716..fe6c672902c0a 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs @@ -13102,18 +13102,6 @@ static void MT2() where T : I1 targetFramework: _supportingFramework); compilation1.VerifyDiagnostics( - // (14,20): error CS0176: Member 'I1.P01' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(this.P01); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.P01").WithArguments("I1.P01").WithLocation(14, 20), - // (15,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(this.P04); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.P04").WithArguments("I1.P04").WithLocation(15, 20), - // (28,20): error CS0176: Member 'I1.P01' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(x.P01); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P01").WithArguments("I1.P01").WithLocation(28, 20), - // (30,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(x.P04); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 20), // (35,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P03); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 20), @@ -13984,18 +13972,6 @@ static void MT2() where T : I1 targetFramework: _supportingFramework); compilation1.VerifyDiagnostics( - // (14,20): error CS0176: Member 'I1.P01' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(this.P01); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.P01").WithArguments("I1.P01").WithLocation(14, 20), - // (15,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(this.P04); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.P04").WithArguments("I1.P04").WithLocation(15, 20), - // (28,20): error CS0176: Member 'I1.P01' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(x.P01); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P01").WithArguments("I1.P01").WithLocation(28, 20), - // (30,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead - // _ = nameof(x.P04); - Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 20), // (35,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P03); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 20), diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 4499972614167..2741b9b1d2052 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -11487,5 +11487,65 @@ class Program Await state.AssertCompletionItemsDoNotContainAny("first", "second") End Using End Function + + + Public Async Function NameOf_Flat() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + , + languageVersion:=LanguageVersion.Preview) + + state.SendInvokeCompletionList() + Await state.AssertCompletionItemsContainAll("Property0", "Field0", "Event0") + Await state.AssertCompletionItemsDoNotContainAny("Property1", "Field1", "Event1") + End Using + End Function + + + Public Async Function NameOf_Nested() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + , + languageVersion:=LanguageVersion.Preview) + + state.SendInvokeCompletionList() + Await state.AssertCompletionItemsContainAll("Property1", "Field1", "Event1") + Await state.AssertCompletionItemsDoNotContainAny("Property0", "Field0", "Event0") + End Using + End Function End Class End Namespace From b23694c7d04e7a9d68a59e41929953a96d428475 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 13 Apr 2023 08:44:27 -0500 Subject: [PATCH 47/69] Eliminate intermediate pooling in CreateNodeOperations --- .../Core/Extensions/ListExtensions.cs | 9 ++ .../Formatting/Engine/AbstractFormatEngine.cs | 83 ++++++------------- .../Compiler/Core/Log/FunctionId.cs | 10 +-- 3 files changed, 38 insertions(+), 64 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs index b9695d23e8e1a..879b628a01346 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs @@ -78,5 +78,14 @@ public static int IndexOf(this IList list, Func predicate) return -1; } + + public static void AddRangeWhere(this List list, List collection, Func predicate) + { + foreach (var element in collection) + { + if (predicate(element)) + list.Add(element); + } + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 70cee7b3e27ba..3065d47f59c82 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; @@ -32,7 +32,6 @@ internal abstract partial class AbstractFormatEngine // Intentionally do not trim the capacities of these collections down. We will repeatedly try to format large // files as we edit them and this will produce a lot of garbage as we free the internal array backing the list // over and over again. - private static readonly ObjectPool> s_nodeIteratorPool = new(() => new(), trimOnFree: false); private static readonly ObjectPool> s_tokenPairListPool = new(() => new(), trimOnFree: false); private readonly ChainedFormattingRules _formattingRules; @@ -132,75 +131,41 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella { cancellationToken.ThrowIfCancellationRequested(); - // iterating tree is very expensive. do it once and cache it to list - using var pooledIterator = s_nodeIteratorPool.GetPooledObject(); + List indentBlockOperation = new(); + List suppressOperation = new(); + List alignmentOperation = new(); + List anchorIndentationOperations = new(); - var nodeIterator = pooledIterator.Object; - using (Logger.LogBlock(FunctionId.Formatting_IterateNodes, cancellationToken)) - { - const int magicLengthToNodesRatio = 5; - nodeIterator.Capacity = Math.Max(nodeIterator.Capacity, Math.Max(this.SpanToFormat.Length / magicLengthToNodesRatio, 4)); - - foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) - { - cancellationToken.ThrowIfCancellationRequested(); - nodeIterator.Add(node); - } - } - - // iterate through each operation using index to not create any unnecessary object - cancellationToken.ThrowIfCancellationRequested(); - List indentBlockOperation; - using (Logger.LogBlock(FunctionId.Formatting_CollectIndentBlock, cancellationToken)) - { - indentBlockOperation = AddOperations(nodeIterator, _formattingRules.AddIndentBlockOperations, cancellationToken); - } - - cancellationToken.ThrowIfCancellationRequested(); - List suppressOperation; - using (Logger.LogBlock(FunctionId.Formatting_CollectSuppressOperation, cancellationToken)) - { - suppressOperation = AddOperations(nodeIterator, _formattingRules.AddSuppressOperations, cancellationToken); - } + List indentBlockOperationScratch = new(); + List suppressOperationScratch = new(); + List alignmentOperationScratch = new(); + List anchorIndentationOperationsScratch = new(); - cancellationToken.ThrowIfCancellationRequested(); - List alignmentOperation; - using (Logger.LogBlock(FunctionId.Formatting_CollectAlignOperation, cancellationToken)) + // iterating tree is very expensive. only do it once. + foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { - var operations = AddOperations(nodeIterator, _formattingRules.AddAlignTokensOperations, cancellationToken); - - // make sure we order align operation from left to right - operations.Sort((o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); + cancellationToken.ThrowIfCancellationRequested(); - alignmentOperation = operations; + AddOperations(indentBlockOperation, indentBlockOperationScratch, node, _formattingRules.AddIndentBlockOperations); + AddOperations(suppressOperation, suppressOperationScratch, node, _formattingRules.AddSuppressOperations); + AddOperations(alignmentOperation, alignmentOperationScratch, node, _formattingRules.AddAlignTokensOperations); + AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch, node, _formattingRules.AddAnchorIndentationOperations); } - cancellationToken.ThrowIfCancellationRequested(); - List anchorIndentationOperations; - using (Logger.LogBlock(FunctionId.Formatting_CollectAnchorOperation, cancellationToken)) - { - anchorIndentationOperations = AddOperations(nodeIterator, _formattingRules.AddAnchorIndentationOperations, cancellationToken); - } + // make sure we order align operation from left to right + alignmentOperation.Sort((o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); return new NodeOperations(indentBlockOperation, suppressOperation, anchorIndentationOperations, alignmentOperation); } - private static List AddOperations(SegmentedList nodes, Action, SyntaxNode> addOperations, CancellationToken cancellationToken) + private static void AddOperations(List operations, List scratch, SyntaxNode node, Action, SyntaxNode> addOperations) { - var operations = new List(); - var list = new List(); - - foreach (var n in nodes) - { - cancellationToken.ThrowIfCancellationRequested(); - addOperations(list, n); + Debug.Assert(scratch.Count == 0); - list.RemoveAll(item => item == null); - operations.AddRange(list); - list.Clear(); - } + addOperations(scratch, node); - return operations; + operations.AddRangeWhere(scratch, static item => item is not null); + scratch.Clear(); } private void AddTokenOperations( @@ -329,7 +294,7 @@ private static void ApplySpecialOperations( FormattingContext context, NodeOperations nodeOperationsCollector, OperationApplier applier, CancellationToken cancellationToken) { // apply alignment operation - using (Logger.LogBlock(FunctionId.Formatting_CollectAlignOperation, cancellationToken)) + using (Logger.LogBlock(FunctionId.Formatting_ApplyAlignOperation, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index c0d57e6ac0ee4..fb265dad84e82 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -111,11 +111,11 @@ internal enum FunctionId Formatting_ContextInitialization = 85, Formatting_Format = 86, Formatting_ApplyResultToBuffer = 87, - Formatting_IterateNodes = 88, - Formatting_CollectIndentBlock = 89, - Formatting_CollectSuppressOperation = 90, - Formatting_CollectAlignOperation = 91, - Formatting_CollectAnchorOperation = 92, + // obsolete: Formatting_IterateNodes = 88, + // obsolete: Formatting_CollectIndentBlock = 89, + // obsolete: Formatting_CollectSuppressOperation = 90, + // obsolete: Formatting_CollectAlignOperation = 91, + // obsolete: Formatting_CollectAnchorOperation = 92, Formatting_CollectTokenOperation = 93, Formatting_BuildContext = 94, Formatting_ApplySpaceAndLine = 95, From a61229dded6930e67abd77aa3957589b242a8f3a Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Thu, 13 Apr 2023 07:11:31 -0700 Subject: [PATCH 48/69] Source build fixes (#67747) * Source build fixes The source build produced from our official build needs to include both the net7.0 and net8.0 packages. Those packages feed into the soucre build legs of other repos and they can be targeting either net7.0 or net8.0. * fixes * Fix Work around a bug in the public API analyzer https://github.com/dotnet/roslyn-analyzers/issues/6059 * nint * fix package * fix --- azure-pipelines.yml | 5 +++++ eng/targets/Settings.props | 22 +++++++++++++------ .../Test/CommandLine/CommandLineTests.cs | 6 ++--- .../Diagnostics/DiagnosticSuppressorTests.cs | 2 +- .../ObjectAndCollectionInitializerTests.cs | 2 +- .../MetadataReference/ModuleMetadata.cs | 10 ++++----- .../Core/Portable/PublicAPI.Shipped.txt | 6 ++--- .../Diagnostics/CommonDiagnosticAnalyzers.cs | 6 ++--- .../BindingCollectionInitializerTests.vb | 2 +- ...osoft.Net.Compilers.Toolset.Package.csproj | 11 +++++----- .../Microsoft.Net.Compilers.Toolset.props | 2 +- ...icrosoft.Net.Compilers.Toolset.Arm64.props | 4 +--- .../BuildBoss/CompilerNuGetCheckerUtil.cs | 10 ++++----- 13 files changed, 50 insertions(+), 38 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 806cac131a1a5..805b05793cfc6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -389,3 +389,8 @@ stages: - script: ./eng/build.sh --solution Roslyn.sln --restore --build --configuration Debug --prepareMachine --ci --binaryLog --runanalyzers --warnaserror /p:RoslynEnforceCodeStyle=true displayName: Build with analyzers + + - template: eng/pipelines/publish-logs.yml + parameters: + jobName: Correctness_Analyzers + configuration: Debug diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props index 5e9984f3f52e9..b77e2d242dc9e 100644 --- a/eng/targets/Settings.props +++ b/eng/targets/Settings.props @@ -51,12 +51,21 @@ false false true - - - net6.0 - net8.0;$(SourceBuildTargetFrameworks) - net8.0 + + + $(NetCurrent) + $(NetCurrent);$(NetPrevious) + $(NetCurrent);$(NetPrevious);net6.0 + net6.0 $(SourceBuildTargetFrameworks) $(SourceBuildTargetFrameworksNetFx);net472 @@ -149,8 +158,7 @@ --> - - + diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 8731f0293ac95..cf28edcd5f19a 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -8635,7 +8635,7 @@ public void FileShareDeleteCompatibility_Windows() fsDll.Dispose(); fsPdb.Dispose(); - AssertEx.Equal(new[] { "Lib.cs", "Lib.dll", "Lib.pdb" }, Roslyn.Utilities.EnumerableExtensions.Order(Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)))); + AssertEx.Equal(new[] { "Lib.cs", "Lib.dll", "Lib.pdb" }, Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)).Order()); } /// @@ -8692,7 +8692,7 @@ public void FileShareDeleteCompatibility_Xplat() peDll.Dispose(); pePdb.Dispose(); - AssertEx.Equal(new[] { "Lib.cs", "Lib.dll", "Lib.pdb" }, Roslyn.Utilities.EnumerableExtensions.Order(Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)))); + AssertEx.Equal(new[] { "Lib.cs", "Lib.dll", "Lib.pdb" }, Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)).Order()); // files can be deleted now: File.Delete(libSrc.Path); @@ -8733,7 +8733,7 @@ public void FileShareDeleteCompatibility_ReadOnlyFiles() fsDll.Dispose(); - AssertEx.Equal(new[] { "Lib.cs", "Lib.dll" }, Roslyn.Utilities.EnumerableExtensions.Order(Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)))); + AssertEx.Equal(new[] { "Lib.cs", "Lib.dll" }, Directory.GetFiles(dir.Path).Select(p => Path.GetFileName(p)).Order()); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticSuppressorTests.cs b/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticSuppressorTests.cs index b2808db170bf5..9460fa67eaeb2 100644 --- a/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticSuppressorTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticSuppressorTests.cs @@ -563,7 +563,7 @@ public void TestProgrammaticSuppressionInfo_DiagnosticSuppressor() Assert.Single(diagnostics); var programmaticSuppression = diagnostics.Select(d => d.ProgrammaticSuppressionInfo).Single(); Assert.Equal(2, programmaticSuppression.Suppressions.Count); - var orderedSuppressions = Roslyn.Utilities.EnumerableExtensions.Order(programmaticSuppression.Suppressions).ToImmutableArrayOrEmpty(); + var orderedSuppressions = programmaticSuppression.Suppressions.Order().ToImmutableArrayOrEmpty(); Assert.Equal(suppressionId, orderedSuppressions[0].Id); Assert.Equal(suppressor.SuppressionDescriptor.Justification, orderedSuppressions[0].Justification); Assert.Equal(suppressionId2, orderedSuppressions[1].Id); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs index 61075f1fb3320..764b90baddebe 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs @@ -3510,7 +3510,7 @@ where node.IsKind(SyntaxKind.CollectionInitializerExpression) Assert.Equal(2, symbolInfo.CandidateSymbols.Length); Assert.Equal(new[] {"void X.Add(System.Collections.Generic.List x)", "void X.Add(X x)"}, - Roslyn.Utilities.EnumerableExtensions.Order(symbolInfo.CandidateSymbols.Select(s => s.ToTestDisplayString())).ToArray()); + symbolInfo.CandidateSymbols.Select(s => s.ToTestDisplayString()).Order().ToArray()); } [WorkItem(529787, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529787")] diff --git a/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs b/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs index 5119669dc455c..58d64292d8f73 100644 --- a/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs +++ b/src/Compilers/Core/Portable/MetadataReference/ModuleMetadata.cs @@ -68,7 +68,7 @@ private ModuleMetadata(ModuleMetadata metadata) /// The size of the metadata block. /// is null. /// is not positive. - public static ModuleMetadata CreateFromMetadata(IntPtr metadata, int size) + public static ModuleMetadata CreateFromMetadata(nint metadata, int size) => CreateFromMetadataWorker(metadata, size, onDispose: null); /// @@ -82,7 +82,7 @@ public static ModuleMetadata CreateFromMetadata(IntPtr metadata, int size) /// cref="Metadata.Copy"/> will not call this when they are disposed. /// is null. public static unsafe ModuleMetadata CreateFromMetadata( - IntPtr metadata, + nint metadata, int size, Action onDispose) { @@ -93,11 +93,11 @@ public static unsafe ModuleMetadata CreateFromMetadata( } private static ModuleMetadata CreateFromMetadataWorker( - IntPtr metadata, + nint metadata, int size, Action? onDispose) { - if (metadata == IntPtr.Zero) + if (metadata == 0) { throw new ArgumentNullException(nameof(metadata)); } @@ -124,7 +124,7 @@ internal static ModuleMetadata CreateFromMetadata(IntPtr metadata, int size, boo /// The size of the image pointed to by . /// is null. /// is not positive. - public static unsafe ModuleMetadata CreateFromImage(IntPtr peImage, int size) + public static unsafe ModuleMetadata CreateFromImage(nint peImage, int size) => CreateFromImage((byte*)peImage, size, onDispose: null); private static unsafe ModuleMetadata CreateFromImage(byte* peImage, int size, Action? onDispose) diff --git a/src/Compilers/Core/Portable/PublicAPI.Shipped.txt b/src/Compilers/Core/Portable/PublicAPI.Shipped.txt index 5e8476ffc92f2..b1922d93dbb61 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Shipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Shipped.txt @@ -3577,9 +3577,9 @@ static Microsoft.CodeAnalysis.ModelExtensions.GetTypeInfo(this Microsoft.CodeAna static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromFile(string! path) -> Microsoft.CodeAnalysis.ModuleMetadata! static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromImage(System.Collections.Generic.IEnumerable! peImage) -> Microsoft.CodeAnalysis.ModuleMetadata! static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromImage(System.Collections.Immutable.ImmutableArray peImage) -> Microsoft.CodeAnalysis.ModuleMetadata! -static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromImage(System.IntPtr peImage, int size) -> Microsoft.CodeAnalysis.ModuleMetadata! -static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(System.IntPtr metadata, int size, System.Action! onDispose) -> Microsoft.CodeAnalysis.ModuleMetadata! -static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(System.IntPtr metadata, int size) -> Microsoft.CodeAnalysis.ModuleMetadata! +static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromImage(nint peImage, int size) -> Microsoft.CodeAnalysis.ModuleMetadata! +static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(nint metadata, int size, System.Action! onDispose) -> Microsoft.CodeAnalysis.ModuleMetadata! +static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(nint metadata, int size) -> Microsoft.CodeAnalysis.ModuleMetadata! static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromStream(System.IO.Stream! peStream, bool leaveOpen = false) -> Microsoft.CodeAnalysis.ModuleMetadata! static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromStream(System.IO.Stream! peStream, System.Reflection.PortableExecutable.PEStreamOptions options) -> Microsoft.CodeAnalysis.ModuleMetadata! static Microsoft.CodeAnalysis.NullableContextExtensions.AnnotationsEnabled(this Microsoft.CodeAnalysis.NullableContext context) -> bool diff --git a/src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs b/src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs index a9fc906a5d23d..cadef4ca8a872 100644 --- a/src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs +++ b/src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs @@ -1919,8 +1919,8 @@ void reportDiagnosticsAtCompilationEnd(CompilationAnalysisContext compilationEnd if (!SymbolsStarted.SetEquals(symbolsEnded)) { // Symbols Started: '{0}', Symbols Ended: '{1}', Analyzer: {2} - var symbolsStartedStr = string.Join(", ", Roslyn.Utilities.EnumerableExtensions.Order(SymbolsStarted.Select(s => s.ToDisplayString()))); - var symbolsEndedStr = string.Join(", ", Roslyn.Utilities.EnumerableExtensions.Order(symbolsEnded.Select(s => s.ToDisplayString()))); + var symbolsStartedStr = string.Join(", ", SymbolsStarted.Select(s => s.ToDisplayString().Order())); + var symbolsEndedStr = string.Join(", ", symbolsEnded.Select(s => s.ToDisplayString().Order())); compilationEndContext.ReportDiagnostic(Diagnostic.Create(SymbolStartedEndedDifferRule, Location.None, symbolsStartedStr, symbolsEndedStr, _analyzerId)); } @@ -2268,7 +2268,7 @@ public NamedTypeAnalyzer(AnalysisKind analysisKind, GeneratedCodeAnalysisFlags a } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_rule); - public string GetSortedSymbolCallbacksString() => string.Join(", ", Roslyn.Utilities.EnumerableExtensions.Order(_symbolCallbacks.Select(s => s.Name))); + public string GetSortedSymbolCallbacksString() => string.Join(", ", _symbolCallbacks.Select(s => s.Name).Order()); public override void Initialize(AnalysisContext context) { diff --git a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingCollectionInitializerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingCollectionInitializerTests.vb index 1df82543aa036..077fb5968150b 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingCollectionInitializerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingCollectionInitializerTests.vb @@ -1849,7 +1849,7 @@ End Class Assert.Equal(2, symbolInfo.CandidateSymbols.Length) Assert.Equal({"Sub X.Add(x As System.Collections.Generic.List(Of System.Byte))", "Sub X.Add(x As X)"}, - Roslyn.Utilities.EnumerableExtensions.Order(symbolInfo.CandidateSymbols.Select(Function(s) s.ToTestDisplayString())).ToArray()) + (symbolInfo.CandidateSymbols.Select(Function(s) s.ToTestDisplayString()).Order()).ToArray()) End Sub diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj b/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj index 12f3cfa9bde1f..87639d6b0272f 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj @@ -1,7 +1,8 @@  - $(SourceBuildTargetFrameworksNetFx) + net472;net6.0 + $(NetCurrent) true Microsoft.Net.Compilers.Toolset @@ -51,10 +52,10 @@ <_File Include="@(DesktopCompilerArtifact)" TargetDir="tasks/net472"/> <_File Include="@(DesktopCompilerResourceArtifact)" TargetDir="tasks/net472"/> - <_File Include="@(CoreClrCompilerBuildArtifact)" TargetDir="tasks/$(TargetFramework)"/> - <_File Include="@(CoreClrCompilerToolsArtifact)" TargetDir="tasks/$(TargetFramework)"/> - <_File Include="@(CoreClrCompilerBinArtifact)" TargetDir="tasks/$(TargetFramework)/bincore"/> - <_File Include="@(CoreClrCompilerBinRuntimesArtifact)" TargetDir="tasks/$(TargetFramework)/bincore/runtimes"/> + <_File Include="@(CoreClrCompilerBuildArtifact)" TargetDir="tasks/netcore"/> + <_File Include="@(CoreClrCompilerToolsArtifact)" TargetDir="tasks/netcore"/> + <_File Include="@(CoreClrCompilerBinArtifact)" TargetDir="tasks/netcore/bincore"/> + <_File Include="@(CoreClrCompilerBinRuntimesArtifact)" TargetDir="tasks/netcore/bincore/runtimes"/> diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props b/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props index 4b771afb964cf..8dc5f46a1ecd3 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props @@ -2,7 +2,7 @@ - <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">net6.0 + <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">netcore <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' != 'Core'">net472 <_RoslynTasksDirectory>$(MSBuildThisFileDirectory)..\tasks\$(_RoslynTargetDirectoryName)\ $(_RoslynTasksDirectory)Microsoft.Build.Tasks.CodeAnalysis.dll diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/build/Microsoft.Net.Compilers.Toolset.Arm64.props b/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/build/Microsoft.Net.Compilers.Toolset.Arm64.props index 4b771afb964cf..ea3dd999eb8c4 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/build/Microsoft.Net.Compilers.Toolset.Arm64.props +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/build/Microsoft.Net.Compilers.Toolset.Arm64.props @@ -2,9 +2,7 @@ - <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">net6.0 - <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' != 'Core'">net472 - <_RoslynTasksDirectory>$(MSBuildThisFileDirectory)..\tasks\$(_RoslynTargetDirectoryName)\ + <_RoslynTasksDirectory>$(MSBuildThisFileDirectory)..\tasks\net472\ $(_RoslynTasksDirectory)Microsoft.Build.Tasks.CodeAnalysis.dll true $(_RoslynTasksDirectory)Microsoft.CSharp.Core.targets diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index 2c70bfbe51e47..4e2c621a86750 100644 --- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -138,16 +138,16 @@ private bool CheckPackages(TextWriter textWriter) allGood &= VerifyPackageCore( textWriter, FindNuGetPackage(Path.Combine(ArtifactsDirectory, "packages", Configuration, "Shipping"), "Microsoft.Net.Compilers.Toolset"), - excludeFunc: relativeFileName => relativeFileName.StartsWith(@"tasks\net6.0\bincore\Microsoft.DiaSymReader.Native", PathComparison), + excludeFunc: relativeFileName => relativeFileName.StartsWith(@"tasks\netcore\bincore\Microsoft.DiaSymReader.Native", PathComparison), (@"tasks\net472", GetProjectOutputDirectory("csc", "net472")), (@"tasks\net472", GetProjectOutputDirectory("vbc", "net472")), (@"tasks\net472", GetProjectOutputDirectory("csi", "net472")), (@"tasks\net472", GetProjectOutputDirectory("VBCSCompiler", "net472")), (@"tasks\net472", GetProjectOutputDirectory("Microsoft.Build.Tasks.CodeAnalysis", "net472")), - (@"tasks\net6.0\bincore", GetProjectPublishDirectory("csc", "net6.0")), - (@"tasks\net6.0\bincore", GetProjectPublishDirectory("vbc", "net6.0")), - (@"tasks\net6.0\bincore", GetProjectPublishDirectory("VBCSCompiler", "net6.0")), - (@"tasks\net6.0", GetProjectPublishDirectory("Microsoft.Build.Tasks.CodeAnalysis", "net6.0"))); + (@"tasks\netcore\bincore", GetProjectPublishDirectory("csc", "net6.0")), + (@"tasks\netcore\bincore", GetProjectPublishDirectory("vbc", "net6.0")), + (@"tasks\netcore\bincore", GetProjectPublishDirectory("VBCSCompiler", "net6.0")), + (@"tasks\netcore", GetProjectPublishDirectory("Microsoft.Build.Tasks.CodeAnalysis", "net6.0"))); foreach (var arch in new[] { "x86", "x64", "arm64" }) { From 512f11c28dedda2cdf870e0ed8b547fe3f091a43 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Thu, 13 Apr 2023 07:48:16 -0700 Subject: [PATCH 49/69] Reduce allocations of StateForCase instances (#67779) * In progress * Move to local function * Make satic * Pull worklist up --- .../Portable/Binder/DecisionDagBuilder.cs | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 4a7f3b64ae7a4..21aec51538388 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -38,7 +38,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// /// /// In order to build this automaton, we start (in - /// + /// /// by computing a description of the initial state in a , and then /// for each such state description we decide what the test or evaluation will be at /// that state, and compute the successor state descriptions. @@ -132,7 +132,11 @@ private BoundDecisionDag CreateDecisionDagForIsPattern( LabelSymbol whenTrueLabel) { var rootIdentifier = BoundDagTemp.ForOriginalInput(inputExpression); - return MakeBoundDecisionDag(syntax, ImmutableArray.Create(MakeTestsForPattern(index: 1, pattern.Syntax, rootIdentifier, pattern, whenClause: null, whenTrueLabel))); + var builder = ArrayBuilder.GetInstance(1); + builder.Add(MakeTestsForPattern(index: 1, pattern.Syntax, rootIdentifier, pattern, whenClause: null, whenTrueLabel)); + var result = MakeBoundDecisionDag(syntax, builder); + builder.Free(); + return result; } private BoundDecisionDag CreateDecisionDagForSwitchStatement( @@ -154,7 +158,9 @@ private BoundDecisionDag CreateDecisionDagForSwitchStatement( } } - return MakeBoundDecisionDag(syntax, builder.ToImmutableAndFree()); + var result = MakeBoundDecisionDag(syntax, builder); + builder.Free(); + return result; } /// @@ -171,7 +177,9 @@ private BoundDecisionDag CreateDecisionDagForSwitchExpression( foreach (BoundSwitchExpressionArm arm in switchArms) builder.Add(MakeTestsForPattern(++i, arm.Syntax, rootIdentifier, arm.Pattern, arm.WhenClause, arm.Label)); - return MakeBoundDecisionDag(syntax, builder.ToImmutableAndFree()); + var result = MakeBoundDecisionDag(syntax, builder); + builder.Free(); + return result; } /// @@ -704,10 +712,15 @@ private TypeSymbol ErrorType(string name = "") /// decision when no decision appears to match. This implementation is nonrecursive to avoid /// overflowing the compiler's evaluation stack when compiling a large switch statement. /// - private BoundDecisionDag MakeBoundDecisionDag(SyntaxNode syntax, ImmutableArray cases) + private BoundDecisionDag MakeBoundDecisionDag(SyntaxNode syntax, ArrayBuilder cases) { + // A work list of DagStates whose successors need to be computed + var workList = ArrayBuilder.GetInstance(); + // Build the state machine underlying the decision dag - DecisionDag decisionDag = MakeDecisionDag(cases); + DecisionDag decisionDag = MakeDecisionDag(cases, workList); + + workList.Free(); // Note: It is useful for debugging the dag state table construction to set a breakpoint // here and view `decisionDag.Dump()`. @@ -767,11 +780,10 @@ int tempIdentifier(BoundDagEvaluation e) /// Make a (state machine) starting with the given set of cases in the root node, /// and return the node for the root. /// - private DecisionDag MakeDecisionDag(ImmutableArray casesForRootNode) + private DecisionDag MakeDecisionDag( + ArrayBuilder casesForRootNode, + ArrayBuilder workList) { - // A work list of DagStates whose successors need to be computed - var workList = ArrayBuilder.GetInstance(); - // A mapping used to make each DagState unique (i.e. to de-dup identical states). var uniqueState = new Dictionary(DagStateEquivalence.Instance); @@ -781,7 +793,7 @@ private DecisionDag MakeDecisionDag(ImmutableArray casesForRootNod // so that it is processed only once. This object identity uniqueness will be important later when we // start mutating the DagState nodes to compute successors and BoundDecisionDagNodes // for each one. That is why we have to use an equivalence relation in the dictionary `uniqueState`. - DagState uniqifyState(ImmutableArray cases, ImmutableDictionary remainingValues) + DagState uniquifyState(ImmutableArray cases, ImmutableDictionary remainingValues) { var state = new DagState(cases, remainingValues); if (uniqueState.TryGetValue(state, out DagState? existingState)) @@ -821,7 +833,7 @@ DagState uniqifyState(ImmutableArray cases, ImmutableDictionary.GetInstance(casesForRootNode.Length); + var rewrittenCases = ArrayBuilder.GetInstance(casesForRootNode.Count); foreach (var state in casesForRootNode) { var rewrittenCase = state.RewriteNestedLengthTests(); @@ -832,7 +844,7 @@ DagState uniqifyState(ImmutableArray cases, ImmutableDictionary.Empty); + var initialState = uniquifyState(rewrittenCases.ToImmutableAndFree(), ImmutableDictionary.Empty); // Go through the worklist of DagState nodes for which we have not yet computed // successor states. @@ -867,7 +879,7 @@ DagState uniqifyState(ImmutableArray cases, ImmutableDictionary cases, ImmutableDictionary cases, ImmutableDictionary whenTrueValues, out ImmutableDictionary whenFalseValues, ref foundExplicitNullTest); - state.TrueBranch = uniqifyState(whenTrueDecisions, whenTrueValues); - state.FalseBranch = uniqifyState(whenFalseDecisions, whenFalseValues); + state.TrueBranch = uniquifyState(whenTrueDecisions, whenTrueValues); + state.FalseBranch = uniquifyState(whenFalseDecisions, whenFalseValues); if (foundExplicitNullTest && d is BoundDagNonNullTest { IsExplicitTest: false } t) { // Turn an "implicit" non-null test into an explicit one @@ -914,7 +926,6 @@ DagState uniqifyState(ImmutableArray cases, ImmutableDictionary a.Equals(b)); + return x == y || x.Cases.SequenceEqual(y.Cases, static (a, b) => a.Equals(b)); } public int GetHashCode(DagState x) @@ -1821,7 +1832,7 @@ public int GetHashCode(DagState x) /// As part of the description of a node of the decision automaton, we keep track of what tests /// remain to be done for each case. /// - private sealed class StateForCase + private readonly struct StateForCase { /// /// A number that is distinct for each case and monotonically increasing from earlier to later cases. @@ -1875,9 +1886,7 @@ public bool Equals(StateForCase other) { // We do not include Syntax, Bindings, WhereClause, or CaseLabel // because once the Index is the same, those must be the same too. - return this == other || - other != null && - this.Index == other.Index && + return this.Index == other.Index && this.RemainingTests.Equals(other.RemainingTests); } From 1166deb9eb0417731ceb82a3e526a98a5f9276a1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 11:02:58 -0700 Subject: [PATCH 50/69] Simplify --- .../AbstractAddImportFeatureService.cs | 35 ++-- .../AllSymbolsProjectSearchScope.cs | 11 +- .../MetadataSymbolsSearchScope.cs | 16 +- .../SearchScopes/ProjectSearchScope.cs | 8 +- .../AddImport/SearchScopes/SearchScope.cs | 12 +- .../SourceSymbolsProjectSearchScope.cs | 10 +- .../AddImport/SymbolReferenceFinder.cs | 161 +++++++++--------- 7 files changed, 117 insertions(+), 136 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 88b1a04e5f7e5..079615a612892 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -230,7 +230,7 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( // direct references. i.e. we don't want to search in its metadata references // or in the projects it references itself. We'll be searching those entities // individually. - findTasks.Add(ProcessReferencesTask( + findTasks.Add(ProcessReferencesAsync( allSymbolReferences, maxResults, linkedTokenSource, finder.FindInSourceSymbolsInProjectAsync(projectToAssembly, unreferencedProject, exact, linkedTokenSource.Token))); } @@ -272,7 +272,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( // Second, the SymbolFinder API doesn't even support searching them. if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) { - findTasks.Add(ProcessReferencesTask( + findTasks.Add(ProcessReferencesAsync( allSymbolReferences, maxResults, linkedTokenSource, finder.FindInMetadataSymbolsAsync(assembly, referenceProject, reference, exact, linkedTokenSource.Token))); } @@ -313,33 +313,29 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( return result.ToImmutableAndFree(); } -#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods - private static async Task ProcessReferencesTask( + private static async Task ProcessReferencesAsync( ConcurrentQueue allSymbolReferences, int maxResults, CancellationTokenSource linkedTokenSource, Task> task) { // Wait for either the task to finish, or the linked token to fire. - var finishedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, linkedTokenSource.Token)).ConfigureAwait(false); - if (finishedTask == task) - { - var result = await task.ConfigureAwait(false); - AddRange(allSymbolReferences, result); + var result = await task.ConfigureAwait(false); + AddRange(allSymbolReferences, result); - if (allSymbolReferences.Count >= maxResults) + // If we've gone over the max amount of items we're looking for, attempt to cancel all existing work that is + // still searching. + if (allSymbolReferences.Count >= maxResults) + { + try + { + linkedTokenSource.Cancel(); + } + catch (ObjectDisposedException) { - try - { - linkedTokenSource.Cancel(); - } - catch (ObjectDisposedException) - { - } } } } -#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods /// /// We ignore references that are in a directory that contains the names @@ -441,8 +437,7 @@ private static HashSet GetViableUnreferencedProjects(Project project) return viableProjects; } - private static void AddRange(ConcurrentQueue allSymbolReferences, ImmutableArray proposedReferences) - where TReference : Reference + private static void AddRange(ConcurrentQueue allSymbolReferences, ImmutableArray proposedReferences) { foreach (var reference in proposedReferences) allSymbolReferences.Enqueue(reference); diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/AllSymbolsProjectSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/AllSymbolsProjectSearchScope.cs index f17a00a205ede..19c8c01f38dc9 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/AllSymbolsProjectSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/AllSymbolsProjectSearchScope.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -23,17 +21,16 @@ private class AllSymbolsProjectSearchScope : ProjectSearchScope public AllSymbolsProjectSearchScope( AbstractAddImportFeatureService provider, Project project, - bool exact, - CancellationToken cancellationToken) - : base(provider, project, exact, cancellationToken) + bool exact) + : base(provider, project, exact) { } protected override async Task> FindDeclarationsAsync( - SymbolFilter filter, SearchQuery searchQuery) + SymbolFilter filter, SearchQuery searchQuery, CancellationToken cancellationToken) { var declarations = await DeclarationFinder.FindAllDeclarationsWithNormalQueryAsync( - _project, searchQuery, filter, CancellationToken).ConfigureAwait(false); + _project, searchQuery, filter, cancellationToken).ConfigureAwait(false); return declarations; } diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs index 769ae7b42d55d..ef0de1bbe662e 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/MetadataSymbolsSearchScope.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindSymbols.SymbolTree; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.AddImport { @@ -26,9 +23,8 @@ public MetadataSymbolsSearchScope( Project assemblyProject, IAssemblySymbol assembly, PortableExecutableReference metadataReference, - bool exact, - CancellationToken cancellationToken) - : base(provider, exact, cancellationToken) + bool exact) + : base(provider, exact) { _assemblyProject = assemblyProject; _assembly = assembly; @@ -45,15 +41,15 @@ public override SymbolReference CreateReference(SymbolResult searchResult) } protected override async Task> FindDeclarationsAsync( - SymbolFilter filter, SearchQuery searchQuery) + SymbolFilter filter, SearchQuery searchQuery, CancellationToken cancellationToken) { - var service = _assemblyProject.Solution.Services.GetService(); - var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_assemblyProject, _metadataReference, CancellationToken).ConfigureAwait(false); + var service = _assemblyProject.Solution.Services.GetRequiredService(); + var info = await service.TryGetPotentiallyStaleMetadataSymbolTreeInfoAsync(_assemblyProject, _metadataReference, cancellationToken).ConfigureAwait(false); if (info == null) return ImmutableArray.Empty; var declarations = await info.FindAsync( - searchQuery, _assembly, filter, CancellationToken).ConfigureAwait(false); + searchQuery, _assembly, filter, cancellationToken).ConfigureAwait(false); return declarations; } diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/ProjectSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/ProjectSearchScope.cs index 7f464fc831898..1011a9a00652e 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/ProjectSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/ProjectSearchScope.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.AddImport @@ -18,9 +15,8 @@ private abstract class ProjectSearchScope : SearchScope public ProjectSearchScope( AbstractAddImportFeatureService provider, Project project, - bool exact, - CancellationToken cancellationToken) - : base(provider, exact, cancellationToken) + bool exact) + : base(provider, exact) { Contract.ThrowIfFalse(project.SupportsCompilation); _project = project; diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/SearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/SearchScope.cs index 7305640644ed7..334ff70c0e022 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/SearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/SearchScope.cs @@ -26,28 +26,28 @@ private abstract class SearchScope { public readonly bool Exact; protected readonly AbstractAddImportFeatureService provider; - public readonly CancellationToken CancellationToken; - protected SearchScope(AbstractAddImportFeatureService provider, bool exact, CancellationToken cancellationToken) + protected SearchScope(AbstractAddImportFeatureService provider, bool exact) { this.provider = provider; Exact = exact; - CancellationToken = cancellationToken; } - protected abstract Task> FindDeclarationsAsync(SymbolFilter filter, SearchQuery query); + protected abstract Task> FindDeclarationsAsync(SymbolFilter filter, SearchQuery query, CancellationToken cancellationToken); + public abstract SymbolReference CreateReference(SymbolResult symbol) where T : INamespaceOrTypeSymbol; public async Task>> FindDeclarationsAsync( - string name, TSimpleNameSyntax nameNode, SymbolFilter filter) + string name, TSimpleNameSyntax nameNode, SymbolFilter filter, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (name != null && string.IsNullOrWhiteSpace(name)) { return ImmutableArray>.Empty; } using var query = Exact ? SearchQuery.Create(name, ignoreCase: true) : SearchQuery.CreateFuzzy(name); - var symbols = await FindDeclarationsAsync(filter, query).ConfigureAwait(false); + var symbols = await FindDeclarationsAsync(filter, query, cancellationToken).ConfigureAwait(false); if (Exact) { diff --git a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs index 022ecd693b905..d19ef3ddd6699 100644 --- a/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs +++ b/src/Features/Core/Portable/AddImport/SearchScopes/SourceSymbolsProjectSearchScope.cs @@ -26,17 +26,17 @@ private class SourceSymbolsProjectSearchScope : ProjectSearchScope public SourceSymbolsProjectSearchScope( AbstractAddImportFeatureService provider, ConcurrentDictionary> projectToAssembly, - Project project, bool ignoreCase, CancellationToken cancellationToken) - : base(provider, project, ignoreCase, cancellationToken) + Project project, bool ignoreCase) + : base(provider, project, ignoreCase) { _projectToAssembly = projectToAssembly; } protected override async Task> FindDeclarationsAsync( - SymbolFilter filter, SearchQuery searchQuery) + SymbolFilter filter, SearchQuery searchQuery, CancellationToken cancellationToken) { var service = _project.Solution.Services.GetRequiredService(); - var info = await service.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_project, CancellationToken).ConfigureAwait(false); + var info = await service.TryGetPotentiallyStaleSourceSymbolTreeInfoAsync(_project, cancellationToken).ConfigureAwait(false); if (info == null) { // Looks like there was nothing in the cache. Return no results for now. @@ -49,7 +49,7 @@ protected override async Task> FindDeclarationsAsync( var lazyAssembly = _projectToAssembly.GetOrAdd(_project, CreateLazyAssembly); var declarations = await info.FindAsync( - searchQuery, lazyAssembly, filter, CancellationToken).ConfigureAwait(false); + searchQuery, lazyAssembly, filter, cancellationToken).ConfigureAwait(false); return declarations; diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 63f0d03d3f4f6..b2a07f25194ff 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -87,42 +87,25 @@ private ISet GetNamespacesInScope(CancellationToken cancellati private INamespaceSymbol MapToCompilationNamespaceIfPossible(INamespaceSymbol containingNamespace) => _semanticModel.Compilation.GetCompilationNamespace(containingNamespace) ?? containingNamespace; - internal Task> FindInAllSymbolsInStartingProjectAsync( - bool exact, CancellationToken cancellationToken) - { - var searchScope = new AllSymbolsProjectSearchScope( - _owner, _document.Project, exact, cancellationToken); - return DoAsync(searchScope); - } + internal Task> FindInAllSymbolsInStartingProjectAsync(bool exact, CancellationToken cancellationToken) + => DoAsync(new AllSymbolsProjectSearchScope(_owner, _document.Project, exact), cancellationToken); - internal Task> FindInSourceSymbolsInProjectAsync( - ConcurrentDictionary> projectToAssembly, - Project project, bool exact, CancellationToken cancellationToken) - { - var searchScope = new SourceSymbolsProjectSearchScope( - _owner, projectToAssembly, project, exact, cancellationToken); - return DoAsync(searchScope); - } + internal Task> FindInSourceSymbolsInProjectAsync(ConcurrentDictionary> projectToAssembly, Project project, bool exact, CancellationToken cancellationToken) + => DoAsync(new SourceSymbolsProjectSearchScope(_owner, projectToAssembly, project, exact), cancellationToken); - internal Task> FindInMetadataSymbolsAsync( - IAssemblySymbol assembly, Project assemblyProject, PortableExecutableReference metadataReference, - bool exact, CancellationToken cancellationToken) - { - var searchScope = new MetadataSymbolsSearchScope( - _owner, assemblyProject, assembly, metadataReference, exact, cancellationToken); - return DoAsync(searchScope); - } + internal Task> FindInMetadataSymbolsAsync(IAssemblySymbol assembly, Project assemblyProject, PortableExecutableReference metadataReference, bool exact, CancellationToken cancellationToken) + => DoAsync(new MetadataSymbolsSearchScope(_owner, assemblyProject, assembly, metadataReference, exact), cancellationToken); - private async Task> DoAsync(SearchScope searchScope) + private async Task> DoAsync(SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); // Spin off tasks to do all our searching in parallel using var _1 = ArrayBuilder>>.GetInstance(out var tasks); - tasks.Add(GetReferencesForMatchingTypesAsync(searchScope)); - tasks.Add(GetReferencesForMatchingNamespacesAsync(searchScope)); - tasks.Add(GetReferencesForMatchingFieldsAndPropertiesAsync(searchScope)); - tasks.Add(GetReferencesForMatchingExtensionMethodsAsync(searchScope)); + tasks.Add(GetReferencesForMatchingTypesAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForMatchingNamespacesAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForMatchingFieldsAndPropertiesAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForMatchingExtensionMethodsAsync(searchScope, cancellationToken)); // Searching for things like "Add" (for collection initializers) and "Select" // (for extension methods) should only be done when doing an 'exact' search. @@ -132,16 +115,16 @@ private async Task> DoAsync(SearchScope searchSc // query expression valid. if (searchScope.Exact) { - tasks.Add(GetReferencesForCollectionInitializerMethodsAsync(searchScope)); - tasks.Add(GetReferencesForQueryPatternsAsync(searchScope)); - tasks.Add(GetReferencesForDeconstructAsync(searchScope)); - tasks.Add(GetReferencesForGetAwaiterAsync(searchScope)); - tasks.Add(GetReferencesForGetEnumeratorAsync(searchScope)); - tasks.Add(GetReferencesForGetAsyncEnumeratorAsync(searchScope)); + tasks.Add(GetReferencesForCollectionInitializerMethodsAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForQueryPatternsAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForDeconstructAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForGetAwaiterAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForGetEnumeratorAsync(searchScope, cancellationToken)); + tasks.Add(GetReferencesForGetAsyncEnumeratorAsync(searchScope, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); using var _2 = ArrayBuilder.GetInstance(out var allReferences); foreach (var task in tasks) @@ -180,9 +163,10 @@ private static void CalculateContext( /// to the s or s those types are /// contained in. /// - private async Task> GetReferencesForMatchingTypesAsync(SearchScope searchScope) + private async Task> GetReferencesForMatchingTypesAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (!_owner.CanAddImportForType(_diagnosticId, _node, out var nameNode)) { return ImmutableArray.Empty; @@ -193,18 +177,19 @@ private async Task> GetReferencesForMatchingType out var name, out var arity, out var inAttributeContext, out var hasIncompleteParentMember, out var looksGeneric); - if (ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken: searchScope.CancellationToken)) + if (ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken: cancellationToken)) { // If the expression bound, there's nothing to do. return ImmutableArray.Empty; } - var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Type).ConfigureAwait(false); + var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Type, cancellationToken).ConfigureAwait(false); // also lookup type symbols with the "Attribute" suffix if necessary. if (inAttributeContext) { - var attributeSymbols = await searchScope.FindDeclarationsAsync(name + AttributeSuffix, nameNode, SymbolFilter.Type).ConfigureAwait(false); + var attributeSymbols = await searchScope.FindDeclarationsAsync( + name + AttributeSuffix, nameNode, SymbolFilter.Type, cancellationToken).ConfigureAwait(false); symbols = symbols.AddRange( attributeSymbols.Select(r => r.WithDesiredName(r.DesiredName.GetWithoutAttributeSuffix(isCaseSensitive: false)))); @@ -266,17 +251,17 @@ private bool ArityAccessibilityAndAttributeContextAreCorrect( /// to the s those namespaces are contained in. /// private async Task> GetReferencesForMatchingNamespacesAsync( - SearchScope searchScope) + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForNamespace(_diagnosticId, _node, out var nameNode)) { _syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity); if (arity == 0 && - !ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken: searchScope.CancellationToken)) + !ExpressionBinds(nameNode, checkForExtensionMethods: false, cancellationToken)) { - var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Namespace).ConfigureAwait(false); + var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Namespace, cancellationToken).ConfigureAwait(false); var namespaceSymbols = OfType(symbols); var containingNamespaceSymbols = OfType(symbols).SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace)); @@ -293,9 +278,9 @@ private async Task> GetReferencesForMatchingName /// containing 'Color' as if we import them it can resolve this issue. /// private async Task> GetReferencesForMatchingFieldsAndPropertiesAsync( - SearchScope searchScope) + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out var nameNode) && nameNode != null) { @@ -312,7 +297,7 @@ private async Task> GetReferencesForMatchingFiel if (expression is TSimpleNameSyntax simpleName) { // Check if the expression before the dot binds to a property or field. - var symbol = _semanticModel.GetSymbolInfo(expression, searchScope.CancellationToken).GetAnySymbol(); + var symbol = _semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol(); if (symbol?.Kind is SymbolKind.Property or SymbolKind.Field) { // Check if we have the 'Color Color' case. @@ -322,7 +307,7 @@ private async Task> GetReferencesForMatchingFiel { // Try to look up 'Color' as a type. var symbolResults = await searchScope.FindDeclarationsAsync( - symbol.Name, simpleName, SymbolFilter.Type).ConfigureAwait(false); + symbol.Name, simpleName, SymbolFilter.Type, cancellationToken).ConfigureAwait(false); // Return results that have accessible members. var namedTypeSymbols = OfType(symbolResults); @@ -354,26 +339,27 @@ private bool HasAccessibleStaticFieldOrProperty(INamedTypeSymbol namedType, stri /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForMatchingExtensionMethodsAsync(SearchScope searchScope) + private async Task> GetReferencesForMatchingExtensionMethodsAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out var nameNode) && nameNode != null) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); // See if the name binds. If it does, there's nothing further we need to do. - if (!ExpressionBinds(nameNode, checkForExtensionMethods: true, cancellationToken: searchScope.CancellationToken)) + if (!ExpressionBinds(nameNode, checkForExtensionMethods: true, cancellationToken)) { _syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity); if (name != null) { - var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Member).ConfigureAwait(false); + var symbols = await searchScope.FindDeclarationsAsync(name, nameNode, SymbolFilter.Member, cancellationToken).ConfigureAwait(false); var methodSymbols = OfType(symbols); var extensionMethodSymbols = GetViableExtensionMethods( - methodSymbols, nameNode.Parent, searchScope.CancellationToken); + methodSymbols, nameNode.Parent, cancellationToken); var namespaceSymbols = extensionMethodSymbols.SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace)); return GetNamespaceSymbolReferences(searchScope, namespaceSymbols); @@ -412,15 +398,16 @@ private ImmutableArray> GetViableExtensionMethodsWor /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForCollectionInitializerMethodsAsync(SearchScope searchScope) + private async Task> GetReferencesForCollectionInitializerMethodsAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForMethod(_diagnosticId, _syntaxFacts, _node, out _) && !_syntaxFacts.IsSimpleName(_node) && _owner.IsAddMethodContext(_node, _semanticModel)) { var symbols = await searchScope.FindDeclarationsAsync( - nameof(IList.Add), nameNode: null, filter: SymbolFilter.Member).ConfigureAwait(false); + nameof(IList.Add), nameNode: null, filter: SymbolFilter.Member, cancellationToken).ConfigureAwait(false); // Note: there is no desiredName for these search results. We're searching for // extension methods called "Add", but we have no intention of renaming any @@ -428,7 +415,7 @@ private async Task> GetReferencesForCollectionIn var methodSymbols = OfType(symbols).SelectAsArray(s => s.WithDesiredName(null)); var viableMethods = GetViableExtensionMethods( - methodSymbols, _node.Parent, searchScope.CancellationToken); + methodSymbols, _node.Parent, cancellationToken); return GetNamespaceSymbolReferences(searchScope, viableMethods.SelectAsArray(m => m.WithSymbol(m.Symbol.ContainingNamespace))); @@ -442,18 +429,19 @@ private async Task> GetReferencesForCollectionIn /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForQueryPatternsAsync(SearchScope searchScope) + private async Task> GetReferencesForQueryPatternsAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForQuery(_diagnosticId, _node)) { - var type = _owner.GetQueryClauseInfo(_semanticModel, _node, searchScope.CancellationToken); + var type = _owner.GetQueryClauseInfo(_semanticModel, _node, cancellationToken); if (type != null) { // find extension methods named "Select" return await GetReferencesForExtensionMethodAsync( - searchScope, nameof(Enumerable.Select), type).ConfigureAwait(false); + searchScope, nameof(Enumerable.Select), type, predicate: null, cancellationToken).ConfigureAwait(false); } } @@ -465,17 +453,20 @@ private async Task> GetReferencesForQueryPattern /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForGetAwaiterAsync(SearchScope searchScope) + private async Task> GetReferencesForGetAwaiterAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForGetAwaiter(_diagnosticId, _syntaxFacts, _node)) { var type = GetAwaitInfo(_semanticModel, _syntaxFacts, _node); if (type != null) { - return await GetReferencesForExtensionMethodAsync(searchScope, WellKnownMemberNames.GetAwaiter, type, - m => m.IsValidGetAwaiter()).ConfigureAwait(false); + return await GetReferencesForExtensionMethodAsync( + searchScope, WellKnownMemberNames.GetAwaiter, type, + static m => m.IsValidGetAwaiter(), + cancellationToken).ConfigureAwait(false); } } @@ -487,17 +478,20 @@ private async Task> GetReferencesForGetAwaiterAs /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForGetEnumeratorAsync(SearchScope searchScope) + private async Task> GetReferencesForGetEnumeratorAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForGetEnumerator(_diagnosticId, _syntaxFacts, _node)) { var type = GetCollectionExpressionType(_semanticModel, _syntaxFacts, _node); if (type != null) { - return await GetReferencesForExtensionMethodAsync(searchScope, WellKnownMemberNames.GetEnumeratorMethodName, type, - m => m.IsValidGetEnumerator()).ConfigureAwait(false); + return await GetReferencesForExtensionMethodAsync( + searchScope, WellKnownMemberNames.GetEnumeratorMethodName, type, + static m => m.IsValidGetEnumerator(), + cancellationToken).ConfigureAwait(false); } } @@ -509,17 +503,20 @@ private async Task> GetReferencesForGetEnumerato /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForGetAsyncEnumeratorAsync(SearchScope searchScope) + private async Task> GetReferencesForGetAsyncEnumeratorAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForGetAsyncEnumerator(_diagnosticId, _syntaxFacts, _node)) { var type = GetCollectionExpressionType(_semanticModel, _syntaxFacts, _node); if (type != null) { - return await GetReferencesForExtensionMethodAsync(searchScope, WellKnownMemberNames.GetAsyncEnumeratorMethodName, type, - m => m.IsValidGetAsyncEnumerator()).ConfigureAwait(false); + return await GetReferencesForExtensionMethodAsync( + searchScope, WellKnownMemberNames.GetAsyncEnumeratorMethodName, type, + static m => m.IsValidGetAsyncEnumerator(), + cancellationToken).ConfigureAwait(false); } } @@ -531,13 +528,14 @@ private async Task> GetReferencesForGetAsyncEnum /// s to the s that contain /// the static classes that those extension methods are contained in. /// - private async Task> GetReferencesForDeconstructAsync(SearchScope searchScope) + private async Task> GetReferencesForDeconstructAsync( + SearchScope searchScope, CancellationToken cancellationToken) { - searchScope.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (_owner.CanAddImportForDeconstruct(_diagnosticId, _node)) { - var type = _owner.GetDeconstructInfo(_semanticModel, _node, searchScope.CancellationToken); + var type = _owner.GetDeconstructInfo(_semanticModel, _node, cancellationToken); if (type != null) { // Note: we could check that the extension methods have the right number of out-params. @@ -545,8 +543,7 @@ private async Task> GetReferencesForDeconstructA // we'll just be permissive, with the assumption that there won't be that many matching // 'Deconstruct' extension methods for the type of node that we're on. return await GetReferencesForExtensionMethodAsync( - searchScope, "Deconstruct", type, - m => m.ReturnsVoid).ConfigureAwait(false); + searchScope, "Deconstruct", type, static m => m.ReturnsVoid, cancellationToken).ConfigureAwait(false); } } @@ -554,10 +551,10 @@ private async Task> GetReferencesForDeconstructA } private async Task> GetReferencesForExtensionMethodAsync( - SearchScope searchScope, string name, ITypeSymbol type, Func predicate = null) + SearchScope searchScope, string name, ITypeSymbol type, Func predicate, CancellationToken cancellationToken) { var symbols = await searchScope.FindDeclarationsAsync( - name, nameNode: null, filter: SymbolFilter.Member).ConfigureAwait(false); + name, nameNode: null, filter: SymbolFilter.Member, cancellationToken).ConfigureAwait(false); // Note: there is no "desiredName" when doing this. We're not going to do any // renames of the user code. We're just looking for an extension method called From ce8dfc1b70736dc42bcff60d502d5006beeeae24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 11:03:42 -0700 Subject: [PATCH 51/69] Simplify --- .../AddImport/AbstractAddImportFeatureService.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 079615a612892..b8b7fddb8782b 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -195,11 +195,11 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( - ConcurrentQueue allSymbolReferences, SymbolReferenceFinder finder, - bool exact, CancellationToken cancellationToken) + ConcurrentQueue allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken) { - var references = await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false); - AddRange(allSymbolReferences, references); + AddRange( + allSymbolReferences, + await finder.FindInAllSymbolsInStartingProjectAsync(exact, cancellationToken).ConfigureAwait(false)); } private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( @@ -319,9 +319,7 @@ private static async Task ProcessReferencesAsync( CancellationTokenSource linkedTokenSource, Task> task) { - // Wait for either the task to finish, or the linked token to fire. - var result = await task.ConfigureAwait(false); - AddRange(allSymbolReferences, result); + AddRange(allSymbolReferences, await task.ConfigureAwait(false)); // If we've gone over the max amount of items we're looking for, attempt to cancel all existing work that is // still searching. From 1a6f872862e4593ff923cb62eafd031c590cc8ce Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Thu, 13 Apr 2023 11:11:44 -0700 Subject: [PATCH 52/69] Add ServerGC option --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index bb95c44a768af..8b73440802722 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -31,6 +31,12 @@ "Title"="Run C#/VB code analysis on .Net 6 (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" +[$RootKey$\FeatureFlags\Roslyn\OOPServerGC] +"Description"="Run C#/VB out-of-process code analysis with ServerGC." +"Value"=dword:00000000 +"Title"="Run C#/VB code analysis with ServerGC (requires restart)" +"PreviewPaneChannels"="IntPreview,int.main" + // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag [$RootKey$\FeatureFlags\Lsp\PullDiagnostics] "Description"="Enables the LSP-powered diagnostics for managed .Net projects" From e6e117b4de41189f8554654adff680cb45490f9b Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 13 Apr 2023 11:14:59 -0700 Subject: [PATCH 53/69] Add comment --- .../LanguageServer/Protocol/Extensions/Extensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 7abd01219cfe5..f787f4b061973 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -29,12 +29,16 @@ public static Uri GetURI(this TextDocument document) : ProtocolConversions.GetUriFromFilePath(document.FilePath); } + /// + /// Generate the Uri of a document by replace the name in file path using the document's name. + /// Used to generate the correct Uri when rename a document, because calling doesn't update the file path. + /// public static Uri GetUriFromName(this TextDocument document) { Contract.ThrowIfNull(document.FilePath); Contract.ThrowIfNull(document.Name); - var directoryName = Path.GetDirectoryName(document.FilePath); + Contract.ThrowIfNull(directoryName); var path = Path.Combine(directoryName, document.Name); return document is SourceGeneratedDocument From 87fc9b54c6278264db787ca286211bc527a1839d Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 13 Apr 2023 12:19:43 -0700 Subject: [PATCH 54/69] Clean up after merge --- .../CodeActions/CodeActionResolveHandler.cs | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 51ddace2f306a..4ca1dcffcfebe 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -285,8 +285,8 @@ async Task AddTextDocumentAdditionsAsync( // Create the document as empty textDocumentEdits.Add(new CreateFile { Uri = newTextDoc.GetURI() }); - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); // And then give it content + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); var emptyDocumentRange = new LSP.Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }; var edit = new TextEdit { Range = emptyDocumentRange, NewText = newText.ToString() }; var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; @@ -308,39 +308,43 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); - // If the document has text change. - var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (modifiedDocumentIds.Add(docId)) + { + // If the document has text change. + var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - IEnumerable textChanges; + IEnumerable textChanges; - // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' - // method instead, we would get a change that spans the entire document, which we ideally want to avoid. - if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) - { - Contract.ThrowIfNull(textDiffService); - textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); - } - else - { - var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText); - } + // Normal documents have a unique service for calculating minimal text edits. If we used the standard 'GetTextChanges' + // method instead, we would get a change that spans the entire document, which we ideally want to avoid. + if (newTextDoc is Document newDoc && oldTextDoc is Document oldDoc) + { + Contract.ThrowIfNull(textDiffService); + textChanges = await textDiffService.GetTextChangesAsync(oldDoc, newDoc, cancellationToken).ConfigureAwait(false); + } + else + { + var newText = await newTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); + textChanges = newText.GetTextChanges(oldText); + } - var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + var edits = textChanges.Select(tc => ProtocolConversions.TextChangeToTextEdit(tc, oldText)).ToArray(); + + if (edits.Length > 0) + { + var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; + textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); + } + + // Rename + if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) + { + textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); + } - if (edits.Length > 0) - { - var documentIdentifier = new OptionalVersionedTextDocumentIdentifier { Uri = newTextDoc.GetURI() }; - textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); var linkedDocuments = solution.GetRelatedDocumentIds(docId); modifiedDocumentIds.AddRange(linkedDocuments); } - - // Rename - if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) - { - textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); - } } } } From 793bd64b2b69a2b500b777e0ff495e41ce0e51dc Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 13 Apr 2023 12:23:01 -0700 Subject: [PATCH 55/69] Clean usage of newSolution after merge --- .../Handler/CodeActions/CodeActionResolveHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 4ca1dcffcfebe..64da97f7fb70a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -161,9 +161,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeAction request) } } - if (projectChange.GetChangedDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution)) - || projectChange.GetChangedAdditionalDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution) - || projectChange.GetChangedAnalyzerConfigDocuments().Any(docId => HasDocumentNameChange(docId, applyChangesOperation.ChangedSolution, solution)))) + if (projectChange.GetChangedDocuments().Any(docId => HasDocumentNameChange(docId, newSolution, solution)) + || projectChange.GetChangedAdditionalDocuments().Any(docId => HasDocumentNameChange(docId, newSolution, solution) + || projectChange.GetChangedAnalyzerConfigDocuments().Any(docId => HasDocumentNameChange(docId, newSolution, solution)))) { if (context.GetRequiredClientCapabilities() is not { Workspace.WorkspaceEdit.ResourceOperations: { } resourceOperations } || !resourceOperations.Contains(ResourceOperationKind.Rename)) @@ -308,9 +308,9 @@ async Task AddTextDocumentEditsAsync( Contract.ThrowIfNull(oldTextDoc); Contract.ThrowIfNull(newTextDoc); + // For linked documents, only generated the document edit once. if (modifiedDocumentIds.Add(docId)) { - // If the document has text change. var oldText = await oldTextDoc.GetTextAsync(cancellationToken).ConfigureAwait(false); IEnumerable textChanges; From a3e1c6a90a83af8633c73ffbf63876709231f173 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Thu, 13 Apr 2023 13:25:55 -0700 Subject: [PATCH 56/69] Fixing source build (#67805) * Fixing source build The Microsoft.Net.Compilers.Toolset package produced from our source build CI leg is the toolset used by other repos as part of their CI build. Those legs can be running on a previous or current .NET SDK / runtime. That means to workin all scenarios our toolset package in the CI leg must target previous runtime / SDK. This is starting to produce an unwieldy amount of MSBuild properties for our target frameworks. I decided to remove the `NetFx` one to cut down on the complexity a bit. Source build already has solutions for stripping out `net472`. For the moment removing our custom solution to simplify a bit so I can get source build off the floor. Can revisit once we get back to a clean state. * pr feedback * unblock dev builds --- eng/targets/Settings.props | 72 ++++++++++++++----- src/Compilers/CSharp/csc/AnyCpu/csc.csproj | 2 +- .../Microsoft.Build.Tasks.CodeAnalysis.csproj | 2 +- .../VBCSCompiler/AnyCpu/VBCSCompiler.csproj | 2 +- .../VisualBasic/vbc/AnyCpu/vbc.csproj | 2 +- src/Interactive/csi/csi.csproj | 2 +- src/Interactive/vbi/vbi.vbproj | 2 +- ...osoft.Net.Compilers.Toolset.Package.csproj | 3 +- ...oft.CodeAnalysis.Workspaces.MSBuild.csproj | 2 +- 9 files changed, 64 insertions(+), 25 deletions(-) diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props index b77e2d242dc9e..f00c32423d9f3 100644 --- a/eng/targets/Settings.props +++ b/eng/targets/Settings.props @@ -51,25 +51,65 @@ false false true + + + - - $(NetCurrent) - $(NetCurrent);$(NetPrevious) - $(NetCurrent);$(NetPrevious);net6.0 - net6.0 - - $(SourceBuildTargetFrameworks) - $(SourceBuildTargetFrameworksNetFx);net472 - + + + $(NetPrevious) + $(SourceBuildToolsetTargetFramework) + $(NetCurrent);$(NetPrevious) + + + + + + + $(NetCurrent) + $(SourceBuildToolsetTargetFramework) + $(NetCurrent) + + + + + + + net6.0 + $(NetCurrent);$(NetPrevious);$(SourceBuildToolsetTargetFramework) + $(SourceBuildToolsetTargetFrameworks) + + + + + + + net6.0 + $(SourceBuildToolsetTargetFramework);net7.0 + $(SourceBuildToolsetTargetFrameworks) + + + - net472;net6.0 - $(NetCurrent) + net472;$(SourceBuildToolsetTargetFramework) true Microsoft.Net.Compilers.Toolset diff --git a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index 1f2b37be84e72..075262492f489 100644 --- a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -5,7 +5,7 @@ Library Microsoft.CodeAnalysis true - $(SourceBuildTargetFrameworksNetFx) + $(SourceBuildTargetFrameworks);net472 $(DefineConstants);WORKSPACE_MSBUILD true From 658df1f02bc775507b08cf09bd12e17447cfd332 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 15:36:03 -0700 Subject: [PATCH 57/69] Add pooling back in --- .../Formatting/Engine/AbstractFormatEngine.cs | 31 +++++++++++-------- .../Compiler/Core/ObjectPools/Extensions.cs | 4 +-- .../Compiler/Core/ObjectPools/PooledObject.cs | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 3065d47f59c82..2b0956d3d706d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -29,6 +29,11 @@ namespace Microsoft.CodeAnalysis.Formatting // that would create too big graph. key for this approach is how to reduce size of graph. internal abstract partial class AbstractFormatEngine { + private static class ListPool + { + public static readonly ObjectPool> Pool = new(() => new(), trimOnFree: false); + } + // Intentionally do not trim the capacities of these collections down. We will repeatedly try to format large // files as we edit them and this will produce a lot of garbage as we free the internal array backing the list // over and over again. @@ -131,29 +136,29 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella { cancellationToken.ThrowIfCancellationRequested(); - List indentBlockOperation = new(); - List suppressOperation = new(); - List alignmentOperation = new(); - List anchorIndentationOperations = new(); + var indentBlockOperation = new List(); + var suppressOperation = new List(); + var alignmentOperation = new List(); + var anchorIndentationOperations = new List(); - List indentBlockOperationScratch = new(); - List suppressOperationScratch = new(); - List alignmentOperationScratch = new(); - List anchorIndentationOperationsScratch = new(); + using var indentBlockOperationScratch = ListPool.Pool.GetPooledObject(); + using var suppressOperationScratch = ListPool.Pool.GetPooledObject(); + using var alignmentOperationScratch = ListPool.Pool.GetPooledObject(); + using var anchorIndentationOperationsScratch = ListPool.Pool.GetPooledObject(); // iterating tree is very expensive. only do it once. foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { cancellationToken.ThrowIfCancellationRequested(); - AddOperations(indentBlockOperation, indentBlockOperationScratch, node, _formattingRules.AddIndentBlockOperations); - AddOperations(suppressOperation, suppressOperationScratch, node, _formattingRules.AddSuppressOperations); - AddOperations(alignmentOperation, alignmentOperationScratch, node, _formattingRules.AddAlignTokensOperations); - AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch, node, _formattingRules.AddAnchorIndentationOperations); + AddOperations(indentBlockOperation, indentBlockOperationScratch.Object, node, _formattingRules.AddIndentBlockOperations); + AddOperations(suppressOperation, suppressOperationScratch.Object, node, _formattingRules.AddSuppressOperations); + AddOperations(alignmentOperation, alignmentOperationScratch.Object, node, _formattingRules.AddAlignTokensOperations); + AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch.Object, node, _formattingRules.AddAnchorIndentationOperations); } // make sure we order align operation from left to right - alignmentOperation.Sort((o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); + alignmentOperation.Sort(static (o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); return new NodeOperations(indentBlockOperation, suppressOperation, anchorIndentationOperations, alignmentOperation); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs index 6fa2595554ba6..f41c902d9fbc5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/Extensions.cs @@ -268,7 +268,7 @@ public static void ClearAndFree(this ObjectPool(this ObjectPool> pool, List list) + public static void ClearAndFree(this ObjectPool> pool, List list, bool trim = true) { if (list == null) { @@ -277,7 +277,7 @@ public static void ClearAndFree(this ObjectPool> pool, List list) list.Clear(); - if (list.Capacity > Threshold) + if (trim && list.Capacity > Threshold) { list.Capacity = Threshold; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs index 67bcbf55d7bd6..9078f5f0fd50c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledObject.cs @@ -134,7 +134,7 @@ private static List Allocator(ObjectPool> pool) => pool.AllocateAndClear(); private static void Releaser(ObjectPool> pool, List obj) - => pool.ClearAndFree(obj); + => pool.ClearAndFree(obj, pool.TrimOnFree); private static SegmentedList Allocator(ObjectPool> pool) => pool.AllocateAndClear(); From 26dd5a68f3a86ef7d6741ceec81aae8470b7d948 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 15:40:07 -0700 Subject: [PATCH 58/69] Allocate functions once --- .../Core/Formatting/Engine/AbstractFormatEngine.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 2b0956d3d706d..e29e22d803054 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -146,15 +146,20 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella using var alignmentOperationScratch = ListPool.Pool.GetPooledObject(); using var anchorIndentationOperationsScratch = ListPool.Pool.GetPooledObject(); + var addIndentBlockOperations = _formattingRules.AddIndentBlockOperations; + var addSuppressOperation = _formattingRules.AddSuppressOperations; + var addAlignTokensOperations = _formattingRules.AddAlignTokensOperations; + var addAnchorIndentationOperations = _formattingRules.AddAnchorIndentationOperations; + // iterating tree is very expensive. only do it once. foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { cancellationToken.ThrowIfCancellationRequested(); - AddOperations(indentBlockOperation, indentBlockOperationScratch.Object, node, _formattingRules.AddIndentBlockOperations); - AddOperations(suppressOperation, suppressOperationScratch.Object, node, _formattingRules.AddSuppressOperations); - AddOperations(alignmentOperation, alignmentOperationScratch.Object, node, _formattingRules.AddAlignTokensOperations); - AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch.Object, node, _formattingRules.AddAnchorIndentationOperations); + AddOperations(indentBlockOperation, indentBlockOperationScratch.Object, node, addIndentBlockOperations); + AddOperations(suppressOperation, suppressOperationScratch.Object, node, addSuppressOperation); + AddOperations(alignmentOperation, alignmentOperationScratch.Object, node, addAlignTokensOperations); + AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch.Object, node, addAnchorIndentationOperations); } // make sure we order align operation from left to right From c2d648639c30380e4ced9dc988c3ad20a6372b74 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 15:40:33 -0700 Subject: [PATCH 59/69] move --- .../Core/Formatting/Engine/AbstractFormatEngine.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index e29e22d803054..1477bc6576531 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -141,16 +141,16 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella var alignmentOperation = new List(); var anchorIndentationOperations = new List(); - using var indentBlockOperationScratch = ListPool.Pool.GetPooledObject(); - using var suppressOperationScratch = ListPool.Pool.GetPooledObject(); - using var alignmentOperationScratch = ListPool.Pool.GetPooledObject(); - using var anchorIndentationOperationsScratch = ListPool.Pool.GetPooledObject(); - var addIndentBlockOperations = _formattingRules.AddIndentBlockOperations; var addSuppressOperation = _formattingRules.AddSuppressOperations; var addAlignTokensOperations = _formattingRules.AddAlignTokensOperations; var addAnchorIndentationOperations = _formattingRules.AddAnchorIndentationOperations; + using var indentBlockOperationScratch = ListPool.Pool.GetPooledObject(); + using var suppressOperationScratch = ListPool.Pool.GetPooledObject(); + using var alignmentOperationScratch = ListPool.Pool.GetPooledObject(); + using var anchorIndentationOperationsScratch = ListPool.Pool.GetPooledObject(); + // iterating tree is very expensive. only do it once. foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { From 6fa7359b2bb080065982c967d0ef947eff9be639 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 16:07:28 -0700 Subject: [PATCH 60/69] Simplify --- .../Formatting/Engine/AbstractFormatEngine.cs | 24 +++++++------------ .../Core/Formatting/Engine/NodeOperations.cs | 24 ++++--------------- 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 1477bc6576531..71b68fc5d682f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -29,11 +29,6 @@ namespace Microsoft.CodeAnalysis.Formatting // that would create too big graph. key for this approach is how to reduce size of graph. internal abstract partial class AbstractFormatEngine { - private static class ListPool - { - public static readonly ObjectPool> Pool = new(() => new(), trimOnFree: false); - } - // Intentionally do not trim the capacities of these collections down. We will repeatedly try to format large // files as we edit them and this will produce a lot of garbage as we free the internal array backing the list // over and over again. @@ -136,36 +131,33 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella { cancellationToken.ThrowIfCancellationRequested(); + var nodeOperations = new NodeOperations(); + var indentBlockOperation = new List(); var suppressOperation = new List(); var alignmentOperation = new List(); var anchorIndentationOperations = new List(); + // Cache delegates out here to avoid allocation overhead. + var addIndentBlockOperations = _formattingRules.AddIndentBlockOperations; var addSuppressOperation = _formattingRules.AddSuppressOperations; var addAlignTokensOperations = _formattingRules.AddAlignTokensOperations; var addAnchorIndentationOperations = _formattingRules.AddAnchorIndentationOperations; - using var indentBlockOperationScratch = ListPool.Pool.GetPooledObject(); - using var suppressOperationScratch = ListPool.Pool.GetPooledObject(); - using var alignmentOperationScratch = ListPool.Pool.GetPooledObject(); - using var anchorIndentationOperationsScratch = ListPool.Pool.GetPooledObject(); - // iterating tree is very expensive. only do it once. foreach (var node in _commonRoot.DescendantNodesAndSelf(this.SpanToFormat)) { cancellationToken.ThrowIfCancellationRequested(); - AddOperations(indentBlockOperation, indentBlockOperationScratch.Object, node, addIndentBlockOperations); - AddOperations(suppressOperation, suppressOperationScratch.Object, node, addSuppressOperation); - AddOperations(alignmentOperation, alignmentOperationScratch.Object, node, addAlignTokensOperations); - AddOperations(anchorIndentationOperations, anchorIndentationOperationsScratch.Object, node, addAnchorIndentationOperations); + AddOperations(nodeOperations.IndentBlockOperation, indentBlockOperation, node, addIndentBlockOperations); + AddOperations(nodeOperations.SuppressOperation, suppressOperation, node, addSuppressOperation); + AddOperations(nodeOperations.AlignmentOperation, alignmentOperation, node, addAlignTokensOperations); + AddOperations(nodeOperations.AnchorIndentationOperations, anchorIndentationOperations, node, addAnchorIndentationOperations); } // make sure we order align operation from left to right alignmentOperation.Sort(static (o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); - - return new NodeOperations(indentBlockOperation, suppressOperation, anchorIndentationOperations, alignmentOperation); } private static void AddOperations(List operations, List scratch, SyntaxNode node, Action, SyntaxNode> addOperations) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/NodeOperations.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/NodeOperations.cs index 7a3fbbaa4328c..d76367193f62d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/NodeOperations.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/NodeOperations.cs @@ -14,25 +14,9 @@ internal class NodeOperations { public static NodeOperations Empty = new(); - public List IndentBlockOperation { get; } - public List SuppressOperation { get; } - public List AlignmentOperation { get; } - public List AnchorIndentationOperations { get; } - - public NodeOperations(List indentBlockOperation, List suppressOperation, List anchorIndentationOperations, List alignmentOperation) - { - this.IndentBlockOperation = indentBlockOperation; - this.SuppressOperation = suppressOperation; - this.AlignmentOperation = alignmentOperation; - this.AnchorIndentationOperations = anchorIndentationOperations; - } - - private NodeOperations() - { - this.IndentBlockOperation = new List(); - this.SuppressOperation = new List(); - this.AlignmentOperation = new List(); - this.AnchorIndentationOperations = new List(); - } + public List IndentBlockOperation { get; } = new(); + public List SuppressOperation { get; } = new(); + public List AlignmentOperation { get; } = new(); + public List AnchorIndentationOperations { get; } = new(); } } From 01c0a7585b2d28e8cf8aabfd0fe35c187f80088d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 16:08:36 -0700 Subject: [PATCH 61/69] Fix --- .../Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 71b68fc5d682f..056326d26eaa5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -158,6 +158,8 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella // make sure we order align operation from left to right alignmentOperation.Sort(static (o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); + + return nodeOperations; } private static void AddOperations(List operations, List scratch, SyntaxNode node, Action, SyntaxNode> addOperations) From 6553fd91ec3e2a1142afd4d917197529592be9a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Apr 2023 16:45:41 -0700 Subject: [PATCH 62/69] Include matched spans in our 'container' pattern matcher --- .../AbstractNavigateToSearchService.InProcess.cs | 2 +- .../PatternMatching/ContainerPatternMatcher.cs | 10 ++++------ .../Core/Portable/PatternMatching/PatternMatcher.cs | 11 +++++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 9def945813616..1b27e5df62b91 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -108,7 +108,7 @@ private static async Task ProcessIndexAsync( CancellationToken cancellationToken) { var containerMatcher = patternContainer != null - ? PatternMatcher.CreateDotSeparatedContainerMatcher(patternContainer) + ? PatternMatcher.CreateDotSeparatedContainerMatcher(patternContainer, includeMatchedSpans: true) : null; using var nameMatcher = PatternMatcher.CreatePatternMatcher(patternName, includeMatchedSpans: true, allowFuzzyMatching: true); diff --git a/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs index da4f39eec73c7..eb12898fd5f53 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs @@ -2,12 +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. -#nullable disable - using System; using System.Globalization; using System.Linq; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.PatternMatching @@ -21,9 +18,10 @@ private sealed partial class ContainerPatternMatcher : PatternMatcher public ContainerPatternMatcher( string[] patternParts, char[] containerSplitCharacters, - CultureInfo culture, + bool includeMatchedSpans, + CultureInfo? culture, bool allowFuzzyMatching = false) - : base(false, culture, allowFuzzyMatching) + : base(includeMatchedSpans, culture, allowFuzzyMatching) { _containerSplitCharacters = containerSplitCharacters; @@ -44,7 +42,7 @@ public override void Dispose() } } - public override bool AddMatches(string container, ref TemporaryArray matches) + public override bool AddMatches(string? container, ref TemporaryArray matches) { if (SkipMatch(container)) { diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs index 36c33c465e7be..67153d122d0d2 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared; @@ -46,7 +47,7 @@ internal abstract partial class PatternMatcher : IDisposable /// Whether or not close matches should count as matches. protected PatternMatcher( bool includeMatchedSpans, - CultureInfo culture, + CultureInfo? culture, bool allowFuzzyMatching = false) { culture ??= CultureInfo.CurrentCulture; @@ -74,21 +75,23 @@ public static PatternMatcher CreatePatternMatcher( public static PatternMatcher CreateContainerPatternMatcher( string[] patternParts, char[] containerSplitCharacters, + bool includeMatchedSpans = false, CultureInfo? culture = null, bool allowFuzzyMatching = false) { return new ContainerPatternMatcher( - patternParts, containerSplitCharacters, culture, allowFuzzyMatching); + patternParts, containerSplitCharacters, includeMatchedSpans, culture, allowFuzzyMatching); } public static PatternMatcher CreateDotSeparatedContainerMatcher( string pattern, + bool includeMatchedSpans = false, CultureInfo? culture = null, bool allowFuzzyMatching = false) { return CreateContainerPatternMatcher( pattern.Split(s_dotCharacterArray, StringSplitOptions.RemoveEmptyEntries), - s_dotCharacterArray, culture, allowFuzzyMatching); + s_dotCharacterArray, includeMatchedSpans, culture, allowFuzzyMatching); } internal static (string name, string? containerOpt) GetNameAndContainer(string pattern) @@ -102,7 +105,7 @@ internal static (string name, string? containerOpt) GetNameAndContainer(string p public abstract bool AddMatches(string? candidate, ref TemporaryArray matches); - private bool SkipMatch(string? candidate) + private bool SkipMatch([NotNullWhen(false)] string? candidate) => _invalidPattern || string.IsNullOrWhiteSpace(candidate); private static bool ContainsUpperCaseLetter(string pattern) From 16d40d9f1fcbf828a1f8be4a28a0c086c8a2066e Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 13 Apr 2023 17:22:51 -0700 Subject: [PATCH 63/69] Address feedback --- .../Protocol/Extensions/Extensions.cs | 7 +++---- .../CodeActions/CodeActionResolveHandler.cs | 14 +++++++++----- .../CodeActions/CodeActionResolveTests.cs | 6 ++++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index f787f4b061973..0f2cf01c36cec 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -33,17 +33,16 @@ public static Uri GetURI(this TextDocument document) /// Generate the Uri of a document by replace the name in file path using the document's name. /// Used to generate the correct Uri when rename a document, because calling doesn't update the file path. /// - public static Uri GetUriFromName(this TextDocument document) + public static Uri GetUriForRenamedDocument(this TextDocument document) { Contract.ThrowIfNull(document.FilePath); Contract.ThrowIfNull(document.Name); + Contract.ThrowIfTrue(document is SourceGeneratedDocument); var directoryName = Path.GetDirectoryName(document.FilePath); Contract.ThrowIfNull(directoryName); var path = Path.Combine(directoryName, document.Name); - return document is SourceGeneratedDocument - ? ProtocolConversions.GetUriFromPartialFilePath(path) - : ProtocolConversions.GetUriFromFilePath(path); + return ProtocolConversions.GetUriFromFilePath(path); } public static Uri? TryGetURI(this TextDocument document, RequestContext? context = null) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 64da97f7fb70a..55dff50b59ad9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -336,10 +336,14 @@ async Task AddTextDocumentEditsAsync( textDocumentEdits.Add(new TextDocumentEdit { TextDocument = documentIdentifier, Edits = edits }); } - // Rename + // Add Rename edit. + // Note: + // Client is expected to do the change in the order in which they are provided. + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit + // So we would like to first edit the old document, then rename it. if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) { - textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriFromName(), NewUri = newTextDoc.GetUriFromName() }); + textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriForRenamedDocument(), NewUri = newTextDoc.GetUriForRenamedDocument() }); } var linkedDocuments = solution.GetRelatedDocumentIds(docId); @@ -351,9 +355,9 @@ async Task AddTextDocumentEditsAsync( private static bool HasDocumentNameChange(DocumentId documentId, Solution newSolution, Solution oldSolution) { - var newDocument = newSolution.GetRequiredDocument(documentId); - var oldDocument = oldSolution.GetRequiredDocument(documentId); - return newDocument.State.Name != oldDocument.State.Name; + var newDocument = newSolution.GetRequiredTextDocument(documentId); + var oldDocument = oldSolution.GetRequiredTextDocument(documentId); + return newDocument.Name != oldDocument.Name; } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index e21a6f8ebe542..d5da73cb97b6a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -171,12 +171,12 @@ class {|caret:ABC|} var testWorkspace = testLspServer.TestWorkspace; var documentBefore = testWorkspace.CurrentSolution.GetDocument(testWorkspace.Documents.Single().Id); - var documentUriBefore = documentBefore.GetUriFromName(); + var documentUriBefore = documentBefore.GetUriForRenamedDocument(); var actualResolvedAction = await RunGetCodeActionResolveAsync(testLspServer, unresolvedCodeAction); var documentAfter = testWorkspace.CurrentSolution.GetDocument(testWorkspace.Documents.Single().Id); - var documentUriAfter = documentBefore.WithName("ABC.cs").GetUriFromName(); + var documentUriAfter = documentBefore.WithName("ABC.cs").GetUriForRenamedDocument(); var expectedCodeAction = CodeActionsTests.CreateCodeAction( title: string.Format(FeaturesResources.Rename_file_to_0, "ABC.cs"), @@ -190,6 +190,8 @@ class {|caret:ABC|} applicableRange: new LSP.Range { Start = new Position { Line = 0, Character = 6 }, End = new Position { Line = 0, Character = 9 } }, diagnostics: null, edit: GenerateRenameFileEdit(new List<(Uri, Uri)> { (documentUriBefore, documentUriAfter) })); + + AssertJsonEquals(expectedCodeAction, actualResolvedAction); } [WpfTheory, CombinatorialData] From b4d4a5510df32556135ae4fad491406a5dc9ba46 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 13 Apr 2023 17:25:40 -0700 Subject: [PATCH 64/69] Address feedback --- .../Protocol/Handler/CodeActions/CodeActionResolveHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs index 55dff50b59ad9..4e26e6175c29b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionResolveHandler.cs @@ -341,9 +341,9 @@ async Task AddTextDocumentEditsAsync( // Client is expected to do the change in the order in which they are provided. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit // So we would like to first edit the old document, then rename it. - if (oldTextDoc.State.Attributes.Name != newTextDoc.State.Name) + if (oldTextDoc.Name != newTextDoc.Name) { - textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetUriForRenamedDocument(), NewUri = newTextDoc.GetUriForRenamedDocument() }); + textDocumentEdits.Add(new RenameFile() { OldUri = oldTextDoc.GetURI(), NewUri = newTextDoc.GetUriForRenamedDocument() }); } var linkedDocuments = solution.GetRelatedDocumentIds(docId); From b473dffc83a942e41dc2cdb85d4b7f4b8a2e2f6a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 14 Apr 2023 10:46:51 -0700 Subject: [PATCH 65/69] Use ?. --- .../Workspace/Solution/LoadableTextAndVersionSource.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs index 0eb83a1f03c4a..9639bc617aa50 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs @@ -24,14 +24,15 @@ private sealed class LazyValueWithOptions /// /// Strong reference to the loaded text and version. Only held onto once computed if . is . Once held onto, this will be returned from all calls to - /// , or . + /// , or . Once non-null will always + /// remain non-null. /// private TextAndVersion? _instance; /// /// Weak reference to the loaded text and version that we create whenever the value is computed. We will /// attempt to return from this if still alive when clients call back into this. If neither this, nor are available, the value will be reloaded. + /// cref="_instance"/> are available, the value will be reloaded. Once non-null, this will always be non-null. /// private WeakReference? _weakInstance; @@ -53,7 +54,7 @@ public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value) if (value != null) return true; - return _weakInstance != null && _weakInstance.TryGetTarget(out value) && value != null; + return _weakInstance?.TryGetTarget(out value) == true && value != null; } public TextAndVersion GetValue(CancellationToken cancellationToken) From a70bd52dc38fd945a8e528b06fb9814b9ec60344 Mon Sep 17 00:00:00 2001 From: Ankita Khera <40616383+akhera99@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:39:16 -0700 Subject: [PATCH 66/69] fix crash in inlay hints cache (#67810) --- .../Handler/InlayHint/InlayHintCache.cs | 2 +- .../Handler/InlayHint/InlayHintHandler.cs | 4 +- .../Handler/InlayHint/InlayHintResolveData.cs | 5 +- .../InlayHint/InlayHintResolveHandler.cs | 18 ++++-- .../InlayHint/CSharpInlayHintTests.cs | 59 +++++++++++++++++++ 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintCache.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintCache.cs index 9875aa41296e2..aa98addfc4f9b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintCache.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintCache.cs @@ -18,5 +18,5 @@ public InlayHintCache() : base(maxCacheSize: 3) /// /// Cached data need to resolve a specific inlay hint item. /// - internal record InlayHintCacheEntry(ImmutableArray InlayHintMembers, TextDocumentIdentifier TextDocumentIdentifier, VersionStamp SyntaxVersion); + internal record InlayHintCacheEntry(ImmutableArray InlayHintMembers, VersionStamp SyntaxVersion); } diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs index 41d61987595ed..680a680b6845b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs @@ -56,7 +56,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) // Store the members in the resolve cache so that when we get a resolve request for a particular // member we can re-use the inline hint. - var resultId = inlayHintCache.UpdateCache(new InlayHintCache.InlayHintCacheEntry(hints, request.TextDocument, syntaxVersion)); + var resultId = inlayHintCache.UpdateCache(new InlayHintCache.InlayHintCacheEntry(hints, syntaxVersion)); for (var i = 0; i < hints.Length; i++) { @@ -85,7 +85,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) ToolTip = null, PaddingLeft = leftPadding, PaddingRight = rightPadding, - Data = new InlayHintResolveData(resultId, i) + Data = new InlayHintResolveData(resultId, i, request.TextDocument) }; inlayHints.Add(inlayHint); diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveData.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveData.cs index 70546bd0bd83f..ea549b4facc50 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveData.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveData.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.VisualStudio.LanguageServer.Protocol; + namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint; /// @@ -9,4 +11,5 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint; /// /// the resultId associated with the inlay hint created on original request. /// the index of the specific inlay hint item in the original list. -internal sealed record InlayHintResolveData(long ResultId, int ListIndex); +/// /// the text document associated with the inlay hint to resolve. +internal sealed record InlayHintResolveData(long ResultId, int ListIndex, TextDocumentIdentifier TextDocument); diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveHandler.cs index 5c627f36bf7af..cad8944a8c64f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintResolveHandler.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.InlineHints; +using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeLens; using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json.Linq; using Roslyn.Utilities; @@ -32,12 +33,13 @@ public InlayHintResolveHandler(InlayHintCache inlayHintCache) public bool RequiresLSPSolution => true; public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.InlayHint request) - => GetCacheEntry(request).CacheEntry.TextDocumentIdentifier; + => GetInlayHintResolveData(request).TextDocument; public async Task HandleRequestAsync(LSP.InlayHint request, RequestContext context, CancellationToken cancellationToken) { var document = context.GetRequiredDocument(); - var (cacheEntry, inlineHintToResolve) = GetCacheEntry(request); + var resolveData = GetInlayHintResolveData(request); + var (cacheEntry, inlineHintToResolve) = GetCacheEntry(resolveData); var currentSyntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false); var cachedSyntaxVersion = cacheEntry.SyntaxVersion; @@ -56,14 +58,18 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.InlayHint request) return request; } - private (InlayHintCache.InlayHintCacheEntry CacheEntry, InlineHint InlineHintToResolve) GetCacheEntry(LSP.InlayHint request) + private (InlayHintCache.InlayHintCacheEntry CacheEntry, InlineHint InlineHintToResolve) GetCacheEntry(InlayHintResolveData resolveData) { - var resolveData = (request.Data as JToken)?.ToObject(); - Contract.ThrowIfNull(resolveData, "Missing data for inlay hint resolve request"); - var cacheEntry = _inlayHintCache.GetCachedEntry(resolveData.ResultId); Contract.ThrowIfNull(cacheEntry, "Missing cache entry for inlay hint resolve request"); return (cacheEntry, cacheEntry.InlayHintMembers[resolveData.ListIndex]); } + + private static InlayHintResolveData GetInlayHintResolveData(LSP.InlayHint inlayHint) + { + var resolveData = (inlayHint.Data as JToken)?.ToObject(); + Contract.ThrowIfNull(resolveData, "Missing data for inlay hint resolve request"); + return resolveData; + } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs index 3093ea047200d..345c243e74a9b 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs @@ -6,10 +6,17 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.InlineHints; +using Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint; +using Microsoft.CodeAnalysis.Text; +using Newtonsoft.Json; +using Roslyn.Test.Utilities; +using StreamJsonRpc; using Xunit; using Xunit.Abstractions; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.InlayHint { @@ -99,6 +106,58 @@ void X((int, bool) d) await RunVerifyInlayHintAsync(markup, mutatingLspWorkspace, hasTextEdits: false); } + [Theory, CombinatorialData] + public async Task TestDoesNotShutdownServerIfCacheEntryMissing(bool mutatingLspWorkspace) + { + var markup = +@"class A +{ + void M() + { + var {|int:|}x = 5; + } +}"; + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, CapabilitiesWithVSExtensions); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(InlineHintsOptionsStorage.EnabledForParameters, LanguageNames.CSharp, true); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(InlineHintsOptionsStorage.EnabledForTypes, LanguageNames.CSharp, true); + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var textDocument = CreateTextDocumentIdentifier(document.GetURI()); + var sourceText = await document.GetTextAsync(); + var span = TextSpan.FromBounds(0, sourceText.Length); + + var inlayHintParams = new LSP.InlayHintParams + { + TextDocument = textDocument, + Range = ProtocolConversions.TextSpanToRange(span, sourceText) + }; + + var actualInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + var firstInlayHint = actualInlayHints.First(); + var data = JsonConvert.DeserializeObject(firstInlayHint.Data!.ToString()); + AssertEx.NotNull(data); + var firstResultId = data.ResultId; + + // Verify the inlay hint item is in the cache. + var cache = testLspServer.GetRequiredLspService(); + Assert.NotNull(cache.GetCachedEntry(firstResultId)); + + // Execute a few more requests to ensure the first request is removed from the cache. + await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + var lastInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + Assert.True(lastInlayHints.Any()); + + // Assert that the first result id is no longer in the cache. + Assert.Null(cache.GetCachedEntry(firstResultId)); + + // Assert that the request throws because the item no longer exists in the cache. + await Assert.ThrowsAsync(async () => await testLspServer.ExecuteRequestAsync(LSP.Methods.InlayHintResolveName, firstInlayHint, CancellationToken.None)); + + // Assert that the server did not shutdown and that we can resolve the latest inlay hint request we made. + var lastInlayHint = await testLspServer.ExecuteRequestAsync(LSP.Methods.InlayHintResolveName, lastInlayHints.First(), CancellationToken.None); + Assert.NotNull(lastInlayHint?.ToolTip); + } + private async Task RunVerifyInlayHintAsync(string markup, bool mutatingLspWorkspace, bool hasTextEdits = true) { await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, CapabilitiesWithVSExtensions); From 5ba8dd42256cf4639184c4b1312c256b36e9a04f Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 14 Apr 2023 11:51:55 -0700 Subject: [PATCH 67/69] Defer creation of Location objects to the point they are actually needed. (#67742) * Continued removal of location allocations * Continued removal of location allocations * Only compute once * Defer location allocation * Defer more location creation * Apply suggestions from code review * Update src/Compilers/CSharp/Portable/Binder/Binder.cs * Simplify * Fix * Move into assert --- .../CSharp/Portable/Binder/Binder.cs | 7 ++-- .../Portable/Binder/Binder_Conversions.cs | 2 +- .../Portable/Binder/Binder_Invocation.cs | 2 +- .../Portable/Binder/BindingDiagnosticBag.cs | 5 ++- .../Portable/Binder/ForEachLoopBinder.cs | 2 +- .../Portable/FlowAnalysis/NullableWalker.cs | 32 +++++++++++-------- ...dinaryMethodOrUserDefinedOperatorSymbol.cs | 17 ++++++---- 7 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.cs index ca2362131ea6c..a47b97af47889 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.cs @@ -739,7 +739,7 @@ internal static void ReportDiagnosticsIfObsoleteInternal(BindingDiagnosticBag di } } - internal static void ReportDiagnosticsIfUnmanagedCallersOnly(BindingDiagnosticBag diagnostics, MethodSymbol symbol, Location location, bool isDelegateConversion) + internal static void ReportDiagnosticsIfUnmanagedCallersOnly(BindingDiagnosticBag diagnostics, MethodSymbol symbol, SyntaxNodeOrToken syntax, bool isDelegateConversion) { var unmanagedCallersOnlyAttributeData = symbol.GetUnmanagedCallersOnlyAttributeData(forceComplete: false); if (unmanagedCallersOnlyAttributeData != null) @@ -747,13 +747,14 @@ internal static void ReportDiagnosticsIfUnmanagedCallersOnly(BindingDiagnosticBa // Either we haven't yet bound the attributes of this method, or there is an UnmanagedCallersOnly present. // In the former case, we use a lazy diagnostic that may end up being ignored later, to avoid causing a // binding cycle. + Debug.Assert(syntax.GetLocation() != null); diagnostics.Add(unmanagedCallersOnlyAttributeData == UnmanagedCallersOnlyAttributeData.Uninitialized - ? (DiagnosticInfo)new LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo(symbol, isDelegateConversion) + ? new LazyUnmanagedCallersOnlyMethodCalledDiagnosticInfo(symbol, isDelegateConversion) : new CSDiagnosticInfo(isDelegateConversion ? ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeConvertedToDelegate : ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, symbol), - location); + syntax.GetLocation()!); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 525e00a5d611b..9f4e83b3466f1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -1477,7 +1477,7 @@ private bool MethodGroupConversionHasErrors( CheckValidScopedMethodConversion(syntax, selectedMethod, delegateOrFuncPtrType, isExtensionMethod, diagnostics); if (!isAddressOf) { - ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, selectedMethod, location, isDelegateConversion: true); + ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, selectedMethod, syntax, isDelegateConversion: true); } ReportDiagnosticsIfObsolete(diagnostics, selectedMethod, syntax, hasBaseReceiver: false); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index fea8c26e413d0..bcd4ef05874b1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1109,7 +1109,7 @@ private BoundCall BindInvocationExpressionContinued( bool hasBaseReceiver = receiver != null && receiver.Kind == BoundKind.BaseReference; ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver); - ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node.Location, isDelegateConversion: false); + ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false); // No use site errors, but there could be use site warnings. // If there are any use site warnings, they have already been reported by overload resolution. diff --git a/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs b/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs index 6902fbbb5cbde..d69391916db66 100644 --- a/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs +++ b/src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs @@ -160,7 +160,10 @@ internal CSDiagnosticInfo Add(ErrorCode code, Location location) return info; } - internal CSDiagnosticInfo Add(ErrorCode code, SyntaxNodeOrToken syntax, params object[] args) + internal CSDiagnosticInfo Add(ErrorCode code, SyntaxNode syntax, params object[] args) + => Add(code, syntax.Location, args); + + internal CSDiagnosticInfo Add(ErrorCode code, SyntaxToken syntax, params object[] args) => Add(code, syntax.GetLocation()!, args); internal CSDiagnosticInfo Add(ErrorCode code, Location location, params object[] args) diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index ef254301c1296..6c22b41537c6f 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -432,7 +432,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno var foreachKeyword = _syntax.ForEachKeyword; ReportDiagnosticsIfObsolete(diagnostics, getEnumeratorMethod, foreachKeyword, hasBaseReceiver: false); - ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, getEnumeratorMethod, foreachKeyword.GetLocation(), isDelegateConversion: false); + ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, getEnumeratorMethod, foreachKeyword, isDelegateConversion: false); // MoveNext is an instance method, so it does not need to have unmanaged callers only diagnostics reported. // Either a diagnostic was reported at the declaration of the method (for the invalid attribute), or MoveNext // is marked as not supported and we won't get here in the first place (for metadata import). diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index f77b69e6d2038..5fa8cc561098a 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -7920,7 +7920,7 @@ private TypeWithState VisitConversion( bool extensionMethodThisArgument = false, Optional stateForLambda = default, bool trackMembers = false, - Location? diagnosticLocationOpt = null, + Location? diagnosticLocation = null, ArrayBuilder? previousArgumentConversionResults = null) { Debug.Assert(!trackMembers || !IsConditionalState); @@ -7957,7 +7957,6 @@ private TypeWithState VisitConversion( NullableFlowState resultState = NullableFlowState.NotNull; bool canConvertNestedNullability = true; bool isSuppressed = false; - diagnosticLocationOpt ??= (conversionOpt ?? conversionOperand).Syntax.GetLocation(); if (conversionOperand.IsSuppressed == true) { @@ -7985,7 +7984,7 @@ private TypeWithState VisitConversion( } if (reportRemainingWarnings && invokeSignature != null) { - ReportNullabilityMismatchWithTargetDelegate(diagnosticLocationOpt, targetType, invokeSignature, method, conversion.IsExtensionMethod); + ReportNullabilityMismatchWithTargetDelegate(getDiagnosticLocation(), targetType, invokeSignature, method, conversion.IsExtensionMethod); } } resultState = NullableFlowState.NotNull; @@ -8007,7 +8006,7 @@ private TypeWithState VisitConversion( VisitLambda(lambda, delegateType, stateForLambda); if (reportRemainingWarnings && delegateType is not null) { - ReportNullabilityMismatchWithTargetDelegate(diagnosticLocationOpt, delegateType, lambda); + ReportNullabilityMismatchWithTargetDelegate(getDiagnosticLocation(), delegateType, lambda); } TrackAnalyzedNullabilityThroughConversionGroup(targetTypeWithNullability.ToTypeWithState(), conversionOpt, conversionOperand); @@ -8038,7 +8037,7 @@ private TypeWithState VisitConversion( case ConversionKind.ExplicitUserDefined: case ConversionKind.ImplicitUserDefined: - return VisitUserDefinedConversion(conversionOpt, conversionOperand, conversion, targetTypeWithNullability, operandType, useLegacyWarnings, assignmentKind, parameterOpt, reportTopLevelWarnings, reportRemainingWarnings, diagnosticLocationOpt); + return VisitUserDefinedConversion(conversionOpt, conversionOperand, conversion, targetTypeWithNullability, operandType, useLegacyWarnings, assignmentKind, parameterOpt, reportTopLevelWarnings, reportRemainingWarnings, getDiagnosticLocation()); case ConversionKind.ExplicitDynamic: case ConversionKind.ImplicitDynamic: @@ -8054,7 +8053,7 @@ private TypeWithState VisitConversion( { if (!operandType.IsNotNull && reportRemainingWarnings) { - ReportDiagnostic(ErrorCode.WRN_UnboxPossibleNull, diagnosticLocationOpt); + ReportDiagnostic(ErrorCode.WRN_UnboxPossibleNull, getDiagnosticLocation()); } LearnFromNonNullTest(conversionOperand, ref State); @@ -8148,7 +8147,7 @@ private TypeWithState VisitConversion( // Explicit conversion of Nullable to T is equivalent to Nullable.Value. if (reportTopLevelWarnings && operandType.MayBeNull) { - ReportDiagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, diagnosticLocationOpt); + ReportDiagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, getDiagnosticLocation()); } // Mark the value as not nullable, regardless of whether it was known to be nullable, @@ -8214,17 +8213,17 @@ private TypeWithState VisitConversion( // Need to report all warnings that apply since the warnings can be suppressed individually. if (reportTopLevelWarnings) { - ReportNullableAssignmentIfNecessary(conversionOperand, targetTypeWithNullability, resultType, useLegacyWarnings, assignmentKind, parameterOpt, diagnosticLocationOpt); + ReportNullableAssignmentIfNecessary(conversionOperand, targetTypeWithNullability, resultType, useLegacyWarnings, assignmentKind, parameterOpt, getDiagnosticLocation()); } if (reportRemainingWarnings && !canConvertNestedNullability) { if (assignmentKind == AssignmentKind.Argument) { - ReportNullabilityMismatchInArgument(diagnosticLocationOpt, operandType.Type, parameterOpt, targetType, forOutput: false); + ReportNullabilityMismatchInArgument(getDiagnosticLocation(), operandType.Type, parameterOpt, targetType, forOutput: false); } else { - ReportNullabilityMismatchInAssignment(diagnosticLocationOpt, GetTypeAsDiagnosticArgument(operandType.Type), targetType); + ReportNullabilityMismatchInAssignment(getDiagnosticLocation(), GetTypeAsDiagnosticArgument(operandType.Type), targetType); } } } @@ -8233,6 +8232,13 @@ private TypeWithState VisitConversion( return resultType; + // Avoid realizing the diagnostic location until needed. + Location getDiagnosticLocation() + { + diagnosticLocation ??= (conversionOpt ?? conversionOperand).Syntax.GetLocation(); + return diagnosticLocation; + } + #nullable enable static TypeWithState calculateResultType(TypeWithAnnotations targetTypeWithNullability, bool fromExplicitCast, NullableFlowState resultState, bool isSuppressed, TypeSymbol targetType) { @@ -8475,7 +8481,7 @@ private TypeWithState VisitUserDefinedConversion( parameterOpt, reportTopLevelWarnings, reportRemainingWarnings, - diagnosticLocationOpt: diagnosticLocation); + diagnosticLocation: diagnosticLocation); // Update method based on operandType: see https://github.com/dotnet/roslyn/issues/29605. // (see NullableReferenceTypesTests.ImplicitConversions_07). @@ -8688,7 +8694,7 @@ private TypeWithState ClassifyAndVisitConversion( parameterOpt, reportTopLevelWarnings: reportWarnings, reportRemainingWarnings: !fromExplicitCast && reportWarnings, - diagnosticLocationOpt: diagnosticLocation); + diagnosticLocation: diagnosticLocation); } public override BoundNode? VisitDelegateCreationExpression(BoundDelegateCreationExpression node) @@ -10221,7 +10227,7 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node) AssignmentKind.ForEachIterationVariable, reportTopLevelWarnings: true, reportRemainingWarnings: true, - diagnosticLocationOpt: variableLocation); + diagnosticLocation: variableLocation); } // In non-error cases we'll only run this loop a single time. In error cases we'll set the nullability of the VariableType multiple times, but at least end up with something diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs index 9236831630301..0ae85749547cc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs @@ -228,11 +228,10 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, { base.AfterAddingTypeMembersChecks(conversions, diagnostics); - var location = ReturnTypeLocation; + // Defer computing location to avoid unnecessary allocations in most cases. + Location? returnTypeLocation = null; var compilation = DeclaringCompilation; - Debug.Assert(location != null); - // Check constraints on return type and parameters. Note: Dev10 uses the // method name location for any such errors. We'll do the same for return // type errors but for parameter errors, we'll use the parameter location. @@ -249,14 +248,14 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, if (RefKind == RefKind.RefReadOnly) { - compilation.EnsureIsReadOnlyAttributeExists(diagnostics, location, modifyCompilation: true); + compilation.EnsureIsReadOnlyAttributeExists(diagnostics, getReturnTypeLocation(), modifyCompilation: true); } ParameterHelpers.EnsureIsReadOnlyAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); if (compilation.ShouldEmitNativeIntegerAttributes(ReturnType)) { - compilation.EnsureNativeIntegerAttributeExists(diagnostics, location, modifyCompilation: true); + compilation.EnsureNativeIntegerAttributeExists(diagnostics, getReturnTypeLocation(), modifyCompilation: true); } ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); @@ -265,10 +264,16 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, if (compilation.ShouldEmitNullableAttributes(this) && ReturnTypeWithAnnotations.NeedsNullableAttribute()) { - compilation.EnsureNullableAttributeExists(diagnostics, location, modifyCompilation: true); + compilation.EnsureNullableAttributeExists(diagnostics, getReturnTypeLocation(), modifyCompilation: true); } ParameterHelpers.EnsureNullableAttributeExists(compilation, this, Parameters, diagnostics, modifyCompilation: true); + + Location getReturnTypeLocation() + { + returnTypeLocation ??= this.ReturnTypeLocation; + return returnTypeLocation; + } } protected abstract void CheckConstraintsForExplicitInterfaceType(ConversionsBase conversions, BindingDiagnosticBag diagnostics); From bdea5841177f2a684f7bfdb3a7b4703d8c855fb2 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 14 Apr 2023 13:26:57 -0700 Subject: [PATCH 68/69] Switch to TemporaryArrays while topologically sorting graphs (#67780) * Pool arrays used while topologically sorting graphcs * Lint * Formatting * Use temp array * Simplify * Fix comment --- .../Portable/Binder/DecisionDagBuilder.cs | 23 +++------- .../Portable/Binder/SwitchExpressionBinder.cs | 17 +++++--- .../Portable/BoundTree/BoundDecisionDag.cs | 18 +++++--- .../Collections/TopologicalSortTests.cs | 39 +++++++++++------ .../Portable/Collections/TopologicalSort.cs | 43 +++++++++++++++---- 5 files changed, 88 insertions(+), 52 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 21aec51538388..a3e4566b820a9 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -1586,24 +1587,10 @@ public DecisionDag(DagState rootNode) /// /// A successor function used to topologically sort the DagState set. /// - private static ImmutableArray Successor(DagState state) + private static void AddSuccessor(ref TemporaryArray builder, DagState state) { - if (state.TrueBranch != null && state.FalseBranch != null) - { - return ImmutableArray.Create(state.FalseBranch, state.TrueBranch); - } - else if (state.TrueBranch != null) - { - return ImmutableArray.Create(state.TrueBranch); - } - else if (state.FalseBranch != null) - { - return ImmutableArray.Create(state.FalseBranch); - } - else - { - return ImmutableArray.Empty; - } + builder.AddIfNotNull(state.TrueBranch); + builder.AddIfNotNull(state.FalseBranch); } /// @@ -1613,7 +1600,7 @@ private static ImmutableArray Successor(DagState state) /// True if the graph was acyclic. public bool TryGetTopologicallySortedReachableStates(out ImmutableArray result) { - return TopologicalSort.TryIterativeSort(SpecializedCollections.SingletonEnumerable(this.RootNode), Successor, out result); + return TopologicalSort.TryIterativeSort(this.RootNode, AddSuccessor, out result); } #if DEBUG diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs index 463109a589e10..73ff46d298ef9 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -84,7 +85,7 @@ private bool CheckSwitchExpressionExhaustive( // We only report exhaustive warnings when the default label is reachable through some series of // tests that do not include a test in which the value is known to be null. Handling paths with // nulls is the job of the nullable walker. - bool wasAcyclic = TopologicalSort.TryIterativeSort(SpecializedCollections.SingletonEnumerable(decisionDag.RootNode), nonNullSuccessors, out var nodes); + bool wasAcyclic = TopologicalSort.TryIterativeSort(decisionDag.RootNode, addNonNullSuccessors, out var nodes); // Since decisionDag.RootNode is acyclic by construction, its subset of nodes sorted here cannot be cyclic Debug.Assert(wasAcyclic); foreach (var n in nodes) @@ -107,7 +108,7 @@ private bool CheckSwitchExpressionExhaustive( return false; - ImmutableArray nonNullSuccessors(BoundDecisionDagNode n) + static void addNonNullSuccessors(ref TemporaryArray builder, BoundDecisionDagNode n) { switch (n) { @@ -115,14 +116,18 @@ ImmutableArray nonNullSuccessors(BoundDecisionDagNode n) switch (p.Test) { case BoundDagNonNullTest t: // checks that the input is not null - return ImmutableArray.Create(p.WhenTrue); + builder.Add(p.WhenTrue); + return; case BoundDagExplicitNullTest t: // checks that the input is null - return ImmutableArray.Create(p.WhenFalse); + builder.Add(p.WhenFalse); + return; default: - return BoundDecisionDag.Successors(n); + BoundDecisionDag.AddSuccessors(ref builder, n); + return; } default: - return BoundDecisionDag.Successors(n); + BoundDecisionDag.AddSuccessors(ref builder, n); + return; } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs index eec713ae6fbbc..1deac891fe1d9 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs @@ -11,6 +11,7 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -20,18 +21,23 @@ internal partial class BoundDecisionDag private ImmutableHashSet _reachableLabels; private ImmutableArray _topologicallySortedNodes; - internal static ImmutableArray Successors(BoundDecisionDagNode node) + internal static void AddSuccessors(ref TemporaryArray builder, BoundDecisionDagNode node) { switch (node) { case BoundEvaluationDecisionDagNode p: - return ImmutableArray.Create(p.Next); + builder.Add(p.Next); + return; case BoundTestDecisionDagNode p: - return ImmutableArray.Create(p.WhenFalse, p.WhenTrue); + builder.Add(p.WhenFalse); + builder.Add(p.WhenTrue); + return; case BoundLeafDecisionDagNode d: - return ImmutableArray.Empty; + return; case BoundWhenDecisionDagNode w: - return (w.WhenFalse != null) ? ImmutableArray.Create(w.WhenTrue, w.WhenFalse) : ImmutableArray.Create(w.WhenTrue); + builder.Add(w.WhenTrue); + builder.AddIfNotNull(w.WhenFalse); + return; default: throw ExceptionUtilities.UnexpectedValue(node.Kind); } @@ -69,7 +75,7 @@ public ImmutableArray TopologicallySortedNodes if (_topologicallySortedNodes.IsDefault) { // We use an iterative topological sort to avoid overflowing the compiler's runtime stack for a large switch statement. - bool wasAcyclic = TopologicalSort.TryIterativeSort(SpecializedCollections.SingletonEnumerable(this.RootNode), Successors, out _topologicallySortedNodes); + bool wasAcyclic = TopologicalSort.TryIterativeSort(this.RootNode, AddSuccessors, out _topologicallySortedNodes); // Since these nodes were constructed by an isomorphic mapping from a known acyclic graph, it cannot be cyclic Debug.Assert(wasAcyclic); diff --git a/src/Compilers/Core/CodeAnalysisTest/Collections/TopologicalSortTests.cs b/src/Compilers/Core/CodeAnalysisTest/Collections/TopologicalSortTests.cs index 8531686f0d9b0..f17755162b283 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Collections/TopologicalSortTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Collections/TopologicalSortTests.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.Collections; using Roslyn.Test.Utilities; using Xunit; @@ -16,6 +15,12 @@ namespace Microsoft.CodeAnalysis.UnitTests.Collections { public class TopologicalSortTests { + private TopologicalSortAddSuccessors GetAddSuccessorsFunction(int[][] successors) + => GetAddSuccessorsFunction(successors, i => i); + + private static TopologicalSortAddSuccessors GetAddSuccessorsFunction(T[][] successors, Func toInt) + => (ref TemporaryArray builder, T value) => builder.AddRange(successors[toInt(value)].ToImmutableArray()); + [Fact] public void Test01() { @@ -29,8 +34,8 @@ public void Test01() /* 5 */ new int[] { 0, 2 }, }; - Func> succF = x => successors[x]; - var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 4, 5 }, i => succF(i).ToImmutableArray(), out var sorted); + var succF = GetAddSuccessorsFunction(successors); + var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 4, 5 }, succF, out var sorted); Assert.True(wasAcyclic); AssertTopologicallySorted(sorted, succF, "Test01"); Assert.Equal(6, sorted.Length); @@ -50,8 +55,8 @@ public void Test01b() /* 5 */ new string[] { "0", "2" }, }; - Func> succF = x => successors[int.Parse(x)]; - var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { "4", "5" }, i => succF(i).ToImmutableArray(), out var sorted); + var succF = GetAddSuccessorsFunction(successors, x => int.Parse(x)); + var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { "4", "5" }, succF, out var sorted); Assert.True(wasAcyclic); AssertTopologicallySorted(sorted, succF, "Test01"); Assert.Equal(6, sorted.Length); @@ -73,8 +78,8 @@ public void Test02() /* 7 */ new int[] { } }; - Func> succF = x => successors[x]; - var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 1, 6 }, i => succF(i).ToImmutableArray(), out var sorted); + var succF = GetAddSuccessorsFunction(successors); + var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 1, 6 }, succF, out var sorted); Assert.True(wasAcyclic); AssertTopologicallySorted(sorted, succF, "Test02"); Assert.Equal(7, sorted.Length); @@ -97,7 +102,8 @@ public void TestCycle() }; // 1 -> 4 -> 3 -> 5 -> 1 - var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 1 }, x => successors[x].ToImmutableArray(), out var sorted); + var succF = GetAddSuccessorsFunction(successors); + var wasAcyclic = TopologicalSort.TryIterativeSort(new[] { 1 }, succF, out var sorted); Assert.False(wasAcyclic); } @@ -140,8 +146,8 @@ public void TestRandom(int seed) } // Perform a topological sort and check it. - Func> succF = x => successors[x]; - var wasAcyclic = TopologicalSort.TryIterativeSort(Enumerable.Range(0, numberOfNodes).ToArray(), i => succF(i).ToImmutableArray(), out var sorted); + var succF = GetAddSuccessorsFunction(successors); + var wasAcyclic = TopologicalSort.TryIterativeSort(Enumerable.Range(0, numberOfNodes).ToArray(), succF, out var sorted); Assert.True(wasAcyclic); Assert.Equal(numberOfNodes, sorted.Length); AssertTopologicallySorted(sorted, succF, $"TestRandom(seed: {seed})"); @@ -155,7 +161,7 @@ public void TestRandom(int seed) // time. successors[possibleSort[0]] = successors[possibleSort[0]].Concat(new int[] { possibleSort[numberOfNodes - 1] }).ToArray(); - wasAcyclic = TopologicalSort.TryIterativeSort(Enumerable.Range(0, numberOfNodes).ToArray(), i => succF(i).ToImmutableArray(), out sorted); + wasAcyclic = TopologicalSort.TryIterativeSort(Enumerable.Range(0, numberOfNodes).ToArray(), succF, out sorted); Assert.False(wasAcyclic); // where @@ -203,13 +209,18 @@ public void TestLots() } } - private void AssertTopologicallySorted(ImmutableArray sorted, Func> successors, string message = null) + private void AssertTopologicallySorted(ImmutableArray sorted, TopologicalSortAddSuccessors addSuccessors, string? message = null) { var seen = new HashSet(); + using var successors = TemporaryArray.Empty; for (int i = sorted.Length - 1; i >= 0; i--) { var n = sorted[i]; - foreach (var succ in successors(n)) + + successors.Clear(); + addSuccessors(ref successors.AsRef(), n); + + foreach (var succ in successors) { Assert.True(seen.Contains(succ), message); } diff --git a/src/Compilers/Core/Portable/Collections/TopologicalSort.cs b/src/Compilers/Core/Portable/Collections/TopologicalSort.cs index a2ba6c737fb5a..e77263c01a962 100644 --- a/src/Compilers/Core/Portable/Collections/TopologicalSort.cs +++ b/src/Compilers/Core/Portable/Collections/TopologicalSort.cs @@ -2,20 +2,31 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { + internal delegate void TopologicalSortAddSuccessors(ref TemporaryArray builder, TNode node); + /// /// A helper class that contains a topological sort algorithm. /// internal static class TopologicalSort { + public static bool TryIterativeSort( + TNode node, + TopologicalSortAddSuccessors addSuccessors, + out ImmutableArray result) + where TNode : notnull + { + return TryIterativeSort(SpecializedCollections.SingletonEnumerable(node), addSuccessors, out result); + } + /// /// Produce a topological sort of a given directed acyclic graph, given a set of nodes which include all nodes /// that have no predecessors. Any nodes not in the given set, but reachable through successors, will be added @@ -24,14 +35,19 @@ internal static class TopologicalSort /// /// The type of the node /// Any subset of the nodes that includes all nodes with no predecessors - /// A function mapping a node to its set of successors + /// A function that adds successor nodes to a provided . /// A list of all reachable nodes, in which each node always precedes its successors /// true if successful; false if not successful due to cycles in the graph - public static bool TryIterativeSort(IEnumerable nodes, Func> successors, out ImmutableArray result) + public static bool TryIterativeSort( + IEnumerable nodes, + TopologicalSortAddSuccessors addSuccessors, + out ImmutableArray result) where TNode : notnull { // First, count the predecessors of each node - PooledDictionary predecessorCounts = PredecessorCounts(nodes, successors, out ImmutableArray allNodes); + PooledDictionary predecessorCounts = PredecessorCounts(nodes, addSuccessors, out ImmutableArray allNodes); + + using var successors = TemporaryArray.Empty; // Initialize the ready set with those nodes that have no predecessors var ready = ArrayBuilder.GetInstance(); @@ -49,7 +65,11 @@ public static bool TryIterativeSort(IEnumerable nodes, Func(IEnumerable nodes, Func.Empty : resultBuilder.ToImmutable(); + predecessorCounts.Free(); ready.Free(); resultBuilder.Free(); + return !hadCycle; } private static PooledDictionary PredecessorCounts( IEnumerable nodes, - Func> successors, + TopologicalSortAddSuccessors addSuccessors, out ImmutableArray allNodes) where TNode : notnull { @@ -80,6 +102,8 @@ private static PooledDictionary PredecessorCounts( var counted = PooledHashSet.GetInstance(); var toCount = ArrayBuilder.GetInstance(); var allNodesBuilder = ArrayBuilder.GetInstance(); + using var successors = TemporaryArray.Empty; + toCount.AddRange(nodes); while (toCount.Count != 0) { @@ -95,7 +119,10 @@ private static PooledDictionary PredecessorCounts( predecessorCounts.Add(n, 0); } - foreach (var succ in successors(n)) + successors.Clear(); + addSuccessors(ref successors.AsRef(), n); + + foreach (var succ in successors) { toCount.Push(succ); if (predecessorCounts.TryGetValue(succ, out int succPredecessorCount)) From 4230b3ef9423df68c66a21b1fdca1aac0905ecda Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 14 Apr 2023 14:10:22 -0700 Subject: [PATCH 69/69] Add "Inline Arrays" feature to "Language Feature Status.md" (#67827) --- docs/Language Feature Status.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 361f44b3e96c0..fe565913a002f 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,6 +10,7 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | +| [Inline Arrays](https://github.com/dotnet/csharplang/blob/main/proposals/inline-arrays.md) | [InlineArrays](https://github.com/dotnet/roslyn/tree/features/InlineArrays) | [In Progress](https://github.com/dotnet/roslyn/issues/67826) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | | | [Using aliases for any type](https://github.com/dotnet/csharplang/issues/4284) | [UsingAliasTypes](https://github.com/dotnet/roslyn/tree/features/UsingAliasTypes) | [Merged into 17.6.P3](https://github.com/dotnet/roslyn/issues/56323) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv) [cston](https://github.com/cston) | | | [Primary Constructors](https://github.com/dotnet/csharplang/issues/2691) | [PrimaryConstructors](https://github.com/dotnet/roslyn/tree/features/PrimaryConstructors) | [Merged into 17.6.P2](https://github.com/dotnet/roslyn/issues/65697) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [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) |