From a376b5764ff1c90f7608c77da146f350d448d710 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 6 Apr 2020 11:33:32 -0700 Subject: [PATCH 01/15] Provide a way to get the Project/Compilation that any symbol was created from. --- .../FindSymbols/IRemoteSymbolFinder.cs | 12 +- .../SymbolFinder_FindReferences_Current.cs | 2 +- .../Core/Portable/Remote/RemoteArguments.cs | 30 ++-- .../Portable/Workspace/Solution/Solution.cs | 24 ++++ .../SolutionState.CompilationTracker.State.cs | 60 +++++++- .../SolutionState.CompilationTracker.cs | 23 ++- .../SolutionState.SymbolToProjectId.cs | 135 ++++++++++++++++++ .../Workspace/Solution/SolutionState.cs | 2 + .../CodeAnalysisService_SymbolFinder.cs | 52 ++++--- 9 files changed, 294 insertions(+), 46 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs index 8e45ed79e59fb..24169be4667a2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinder.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Remote; @@ -17,19 +17,19 @@ Task FindReferencesAsync(PinnedSolutionInfo solutionInfo, SerializableSymbolAndP Task FindLiteralReferencesAsync(PinnedSolutionInfo solutionInfo, object value, TypeCode typeCode, CancellationToken cancellationToken); - Task> FindAllDeclarationsWithNormalQueryAsync( + Task> FindAllDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string name, SearchKind searchKind, SymbolFilter criteria, CancellationToken cancellationToken); - Task> FindSolutionSourceDeclarationsWithNormalQueryAsync( + Task> FindSolutionSourceDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken); - Task> FindProjectSourceDeclarationsWithNormalQueryAsync( + Task> FindProjectSourceDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken); - Task> FindSolutionSourceDeclarationsWithPatternAsync( + Task> FindSolutionSourceDeclarationsWithPatternAsync( PinnedSolutionInfo solutionInfo, string pattern, SymbolFilter criteria, CancellationToken cancellationToken); - Task> FindProjectSourceDeclarationsWithPatternAsync( + Task> FindProjectSourceDeclarationsWithPatternAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string pattern, SymbolFilter criteria, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs index fbbae24ec72e0..74f071a6c16ea 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Current.cs @@ -46,7 +46,7 @@ internal static async Task FindReferencesAsync( solution, new object[] { - SerializableSymbolAndProjectId.Dehydrate(symbolAndProjectId), + SerializableSymbolAndProjectId.Dehydrate(solution, symbolAndProjectId.Symbol, cancellationToken), documents?.Select(d => d.Id).ToArray(), SerializableFindReferencesSearchOptions.Dehydrate(options), }, diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index cc9e5a7d1cb7a..f42660ecd10d2 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -37,35 +37,47 @@ public FindReferencesSearchOptions Rehydrate() } } + /// + /// Note: for purposes of Equality/Hashing, all that we use is the underlying SymbolKey. That's because nearly all + /// IDE features only care if they're looking at the same symbol, they don't care if the symbol came from a + /// different project or not. i.e. a feature like FAR wants to bucket all references to the 'same' originating + /// symbol even if one reference is to a re-targeted version of that symbol. As such, we do not include the + /// ProjectId when computing the result. + /// internal class SerializableSymbolAndProjectId : IEquatable { public string SymbolKeyData; public ProjectId ProjectId; public override int GetHashCode() - => Hash.Combine(SymbolKeyData, ProjectId.GetHashCode()); + => SymbolKeyData.GetHashCode(); public override bool Equals(object obj) => Equals(obj as SerializableSymbolAndProjectId); public bool Equals(SerializableSymbolAndProjectId other) - => other != null && SymbolKeyData.Equals(other.SymbolKeyData) && ProjectId.Equals(other.ProjectId); + => other != null && SymbolKeyData.Equals(other.SymbolKeyData); public static SerializableSymbolAndProjectId Dehydrate( - IAliasSymbol alias, Document document) + IAliasSymbol alias, Document document, CancellationToken cancellationToken) { return alias == null ? null - : Dehydrate(new SymbolAndProjectId(alias, document.Project.Id)); + : Dehydrate(document.Project.Solution, alias, cancellationToken); } public static SerializableSymbolAndProjectId Dehydrate( - SymbolAndProjectId symbolAndProjectId) + Solution solution, ISymbol symbol, CancellationToken cancellationToken) { + var symbolKey = symbol.GetSymbolKey(cancellationToken); + var projectId = solution.GetExactProjectId(symbol); + if (projectId == null) + throw new ArgumentException("Symbol's project could not be found in the Solution provided"); + return new SerializableSymbolAndProjectId { - SymbolKeyData = symbolAndProjectId.Symbol.GetSymbolKey().ToString(), - ProjectId = symbolAndProjectId.ProjectId + SymbolKeyData = symbolKey.ToString(), + ProjectId = projectId, }; } @@ -163,12 +175,12 @@ internal class SerializableReferenceLocation public CandidateReason CandidateReason { get; set; } public static SerializableReferenceLocation Dehydrate( - ReferenceLocation referenceLocation) + ReferenceLocation referenceLocation, CancellationToken cancellationToken) { return new SerializableReferenceLocation { Document = referenceLocation.Document.Id, - Alias = SerializableSymbolAndProjectId.Dehydrate(referenceLocation.Alias, referenceLocation.Document), + Alias = SerializableSymbolAndProjectId.Dehydrate(referenceLocation.Alias, referenceLocation.Document, cancellationToken), Location = referenceLocation.Location.SourceSpan, IsImplicit = referenceLocation.IsImplicit, SymbolUsageInfo = SerializableSymbolUsageInfo.Dehydrate(referenceLocation.SymbolUsageInfo), diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 28ca4501de79d..fd6d12989f91c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -126,6 +126,30 @@ private static Project CreateProject(ProjectId projectId, Solution solution) return projectState == null ? null : GetProject(projectState.Id); } + /// + /// Given a returns the of the exact it + /// came from. This operation is not defined for s produced from other snapshots. + /// + /// + /// This function differs from in terms of how it + /// treats s. Specifically, say there is the following: + /// + /// + /// Project-A, containing Symbol-A. + /// Project-B, with a reference to Project-A, and usage of Symbol-A. + /// + /// + /// It is possible (with retargeting, and other complex cases) that Symbol-A from Project-B will be a different + /// symbol than Symbol-A from Project-A. However, + /// will always try to return Project-A for either of the Symbol-A's, as it prefers to return the original + /// Source-Project of the original definition, not the project that actually produced the symbol. For many + /// features this is an acceptable abstraction. However, for some cases (Find-References in particular) it is + /// necessary to resolve symbols back to the actual project/compilation that produced them for correctness. + /// + internal ProjectId? GetExactProjectId(ISymbol symbol) + => _state.GetExactProjectId(symbol); + /// /// True if the solution contains the document in one of its projects /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index 03466e2521705..bd32f05ce58e4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Roslyn.Utilities; @@ -30,7 +31,9 @@ private class State /// /// The base that starts with everything empty. /// - public static readonly State Empty = new State(compilation: null, declarationOnlyCompilation: null, generatorDriver: new TrackedGeneratorDriver(null)); + public static readonly State Empty = new State( + compilation: null, declarationOnlyCompilation: null, + generatorDriver: new TrackedGeneratorDriver(null), new ConditionalWeakTable()); /// /// A strong reference to the declaration-only compilation. This compilation isn't used to produce symbols, @@ -46,6 +49,14 @@ private class State public TrackedGeneratorDriver GeneratorDriver { get; } + /// + /// Weak table to the assembly symbols that this compilation tracker has created. This can be used to + /// determine which project an assembly symbol came from after the fact. This is needed as the compilation + /// an assembly came from can GC'ed and further requests to get that compilation (or any of it's assemblies) + /// may produce new assembly symbols. + /// + public readonly ConditionalWeakTable CompilationAssembliesAndModules; + /// /// Specifies whether and all compilations it depends on contain full information or not. This can return /// if the state isn't at the point where it would know, and it's necessary to transition to to figure that out. @@ -57,7 +68,11 @@ private class State /// public virtual ValueSource>? FinalCompilation => null; - protected State(ValueSource>? compilation, Compilation? declarationOnlyCompilation, TrackedGeneratorDriver generatorDriver) + protected State( + ValueSource>? compilation, + Compilation? declarationOnlyCompilation, + TrackedGeneratorDriver generatorDriver, + ConditionalWeakTable compilationAssembliesAndModules) { // Declaration-only compilations should never have any references Contract.ThrowIfTrue(declarationOnlyCompilation != null && declarationOnlyCompilation.ExternalReferences.Any()); @@ -65,6 +80,7 @@ protected State(ValueSource>? compilation, Compilation? de Compilation = compilation; DeclarationOnlyCompilation = declarationOnlyCompilation; GeneratorDriver = generatorDriver; + CompilationAssembliesAndModules = compilationAssembliesAndModules; } public static State Create( @@ -90,6 +106,25 @@ public static ValueSource> CreateValueSource( ? new WeakValueSource(compilation) : (ValueSource>)new ConstantValueSource>(compilation); } + + public static ConditionalWeakTable GetCompilationAssembliesAndModules(Compilation compilation) + { + var result = new ConditionalWeakTable(); + + var compAssembly = compilation.Assembly; + result.Add(compAssembly, compAssembly); + + foreach (var reference in compilation.References) + { + var symbol = compilation.GetAssemblyOrModuleSymbol(reference); + if (symbol == null) + continue; + + result.Add(symbol, symbol); + } + + return result; + } } /// @@ -106,7 +141,8 @@ public InProgressState( ImmutableArray<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> intermediateProjects) : base(compilation: new ConstantValueSource>(inProgressCompilation), declarationOnlyCompilation: null, - generatorDriver: inProgressGeneratorDriver) + generatorDriver: inProgressGeneratorDriver, + GetCompilationAssembliesAndModules(inProgressCompilation)) { Contract.ThrowIfTrue(intermediateProjects.IsDefault); Contract.ThrowIfFalse(intermediateProjects.Length > 0); @@ -121,7 +157,10 @@ public InProgressState( private sealed class LightDeclarationState : State { public LightDeclarationState(Compilation declarationOnlyCompilation) - : base(compilation: null, declarationOnlyCompilation: declarationOnlyCompilation, generatorDriver: new TrackedGeneratorDriver(null)) + : base(compilation: null, + declarationOnlyCompilation: declarationOnlyCompilation, + generatorDriver: new TrackedGeneratorDriver(null), + new ConditionalWeakTable()) { } } @@ -133,7 +172,10 @@ public LightDeclarationState(Compilation declarationOnlyCompilation) private sealed class FullDeclarationState : State { public FullDeclarationState(Compilation declarationCompilation, TrackedGeneratorDriver generatorDriver) - : base(new WeakValueSource(declarationCompilation), declarationCompilation.Clone().RemoveAllReferences(), generatorDriver) + : base(new WeakValueSource(declarationCompilation), + declarationCompilation.Clone().RemoveAllReferences(), + generatorDriver, + GetCompilationAssembliesAndModules(declarationCompilation)) { } } @@ -160,8 +202,12 @@ public FinalState( ValueSource> compilationWithoutGeneratedFilesSource, Compilation compilationWithoutGeneratedFiles, TrackedGeneratorDriver generatorDriver, - bool hasSuccessfullyLoaded) - : base(compilationWithoutGeneratedFilesSource, compilationWithoutGeneratedFiles.Clone().RemoveAllReferences(), generatorDriver) + bool hasSuccessfullyLoaded, + ConditionalWeakTable compilationAssemblies) + : base(compilationWithoutGeneratedFilesSource, + compilationWithoutGeneratedFiles.Clone().RemoveAllReferences(), + generatorDriver, + compilationAssemblies) { HasSuccessfullyLoaded = hasSuccessfullyLoaded; FinalCompilation = finalCompilationSource; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index e6f90d9d0a68c..1bd9bd3a7ed18 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -100,6 +100,13 @@ public bool HasCompilation } } + public bool ContainsAssemblyOrModule(ISymbol assemblyOrModule) + { + Debug.Assert(assemblyOrModule.Kind == SymbolKind.Assembly || assemblyOrModule.Kind == SymbolKind.NetModule); + var state = this.ReadState(); + return state.CompilationAssembliesAndModules.TryGetValue(assemblyOrModule, out _); + } + /// /// Creates a new instance of the compilation info, retaining any already built /// compilation state as the now 'old' state @@ -188,7 +195,8 @@ public CompilationTracker FreezePartialStateWithTree(SolutionState solution, Doc new ConstantValueSource>(inProgressCompilation), inProgressCompilation, generatorDriver: new TrackedGeneratorDriver(null), - hasSuccessfullyLoaded: false)); + hasSuccessfullyLoaded: false, + State.GetCompilationAssembliesAndModules(inProgressCompilation))); } /// @@ -303,7 +311,7 @@ private void GetPartialCompilationState( inProgressCompilation = inProgressCompilation.WithReferences(metadataReferences); } - RecordAssemblySymbols(inProgressCompilation, metadataReferenceToProjectId); + RecordAssemblySymbols(solution, inProgressCompilation, metadataReferenceToProjectId); SolutionLogger.CreatePartialProjectState(); } @@ -702,7 +710,7 @@ private async Task FinalizeCompilationAsync( generatorDriver = new TrackedGeneratorDriver(generatorDriver.GeneratorDriver.RunFullGeneration(compilation, out compilation, out var diagnostics, cancellationToken)); } - RecordAssemblySymbols(compilation, metadataReferenceToProjectId); + RecordAssemblySymbols(solution, compilation, metadataReferenceToProjectId); this.WriteState( new FinalState( @@ -710,7 +718,8 @@ private async Task FinalizeCompilationAsync( State.CreateValueSource(compilationWithoutGeneratedFiles, solution.Services), compilationWithoutGeneratedFiles, generatorDriver, - hasSuccessfullyLoaded), + hasSuccessfullyLoaded, + State.GetCompilationAssembliesAndModules(compilation)), solution.Services); return new CompilationInfo(compilation, hasSuccessfullyLoaded); @@ -721,7 +730,7 @@ private async Task FinalizeCompilationAsync( } } - private void RecordAssemblySymbols(Compilation compilation, Dictionary metadataReferenceToProjectId) + private void RecordAssemblySymbols(SolutionState solutionState, Compilation compilation, Dictionary metadataReferenceToProjectId) { // TODO: Record source assembly to project mapping // RecordSourceOfAssemblySymbol(compilation.Assembly, this.ProjectState.Id); @@ -735,6 +744,10 @@ private void RecordAssemblySymbols(Compilation compilation, Dictionary diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs new file mode 100644 index 0000000000000..d1df6129031aa --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal partial class SolutionState + { + /// + /// Contains caches to help us quickly and accurately map from s to . + /// + /// + /// Note: this is not as this is a struct and will mutate as operations are called on + /// it. However, in the common case where this data is not needed, it will have not have any impact. + /// + private SymbolToProjectId _symbolToProjectId; + + /// + public ProjectId? GetExactProjectId(ISymbol symbol) + => _symbolToProjectId.GetExactProjectId(symbol); + + /// + /// A helper type that can be used to map from symbols to a project they could have come from. + /// + private struct SymbolToProjectId + { + /// + /// Weak cache from a to the ID of the it was created from. + /// Only used for mapping a 's back. These + /// 's do not belong to a or and thus cannot be tracked with . + /// + private static readonly ConditionalWeakTable s_compilationToProjectId + = new ConditionalWeakTable(); + + private readonly SolutionState _solutionState; + + /// + /// Cache we use to map between assembly and module symbols and the project they came from. That way if we + /// are asked about many symbols from the same assembly/module we can answer the question quickly after + /// computing for the first one. + /// + private ConditionalWeakTable? _assemblyOrModuleSymbolToProjectId; + private static readonly Func> s_createTable = () => new ConditionalWeakTable(); + + public SymbolToProjectId(SolutionState solutionState) + { + _solutionState = solutionState; + _assemblyOrModuleSymbolToProjectId = null; + } + + /// . + public ProjectId? GetExactProjectId(ISymbol? symbol) + { + LazyInitialization.EnsureInitialized(ref _assemblyOrModuleSymbolToProjectId, s_createTable); + + // Walk up the symbol so we can get to the containing namespace/assembly that will be used to map + // back to a project. + + while (symbol != null) + { + var result = GetProjectIdDirectly(symbol, _assemblyOrModuleSymbolToProjectId); + if (result != null) + return result; + + symbol = symbol.ContainingSymbol; + } + + return null; + } + + private ProjectId? GetProjectIdDirectly( + ISymbol symbol, ConditionalWeakTable assemblyOrModuleSymbolToProjectId) + { + if (symbol.IsKind(SymbolKind.Namespace, out INamespaceSymbol? ns)) + { + if (ns.ContainingCompilation != null) + { + // A namespace that spans a compilation. These don't belong to an assembly/module. However, we + // can map their compilation directly + s_compilationToProjectId.TryGetValue(ns.ContainingCompilation, out var projectId); + return projectId; + } + } + else if (symbol.IsKind(SymbolKind.Assembly) || + symbol.IsKind(SymbolKind.NetModule)) + { + if (!assemblyOrModuleSymbolToProjectId.TryGetValue(symbol, out var projectId)) + { + foreach (var (id, state) in _solutionState.ProjectStates) + { + var tracker = _solutionState.GetCompilationTracker(id); + if (tracker.ContainsAssemblyOrModule(symbol)) + { + projectId = id; + break; + } + } + + // Have to lock as there's no atomic AddOrUpdate in netstandard2.0 and we could throw if two + // threads tried to add the same item. +#if NETSTANDARD + lock (assemblyOrModuleSymbolToProjectId) + { + assemblyOrModuleSymbolToProjectId.Remove(symbol); + assemblyOrModuleSymbolToProjectId.Add(symbol, projectId); + } +#else + assemblyOrModuleSymbolToProjectId.AddOrUpdate(symbol, projectId); +#endif + } + + return projectId; + } + + return null; + } + + public void RecordCompilation(Compilation compilation, ProjectId id) + { + if (!s_compilationToProjectId.TryGetValue(compilation, out _)) + s_compilationToProjectId.Add(compilation, id); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index da1704b5d7a66..bc7f27bfba02b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -74,6 +74,8 @@ private SolutionState( _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; + _symbolToProjectId = new SymbolToProjectId(this); + // when solution state is changed, we re-calcuate its checksum _lazyChecksums = new AsyncLazy(ComputeChecksumsAsync, cacheResult: true); diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs index e1636d7b252ea..07b3fdb8debf5 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_SymbolFinder.cs @@ -3,12 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -34,7 +34,7 @@ public Task FindReferencesAsync( var symbolAndProjectId = await symbolAndProjectIdArg.TryRehydrateAsync( solution, cancellationToken).ConfigureAwait(false); - var progressCallback = new FindReferencesProgressCallback(EndPoint, cancellationToken); + var progressCallback = new FindReferencesProgressCallback(solution, EndPoint, cancellationToken); if (!symbolAndProjectId.HasValue) { @@ -74,7 +74,17 @@ await SymbolFinder.FindLiteralReferencesInCurrentProcessAsync( }, cancellationToken); } - public Task> FindAllDeclarationsWithNormalQueryAsync( + private ImmutableArray Convert(ImmutableArray items, Solution solution, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var item in items) + result.Add(SerializableSymbolAndProjectId.Dehydrate(solution, item.Symbol, cancellationToken)); + + return result.ToImmutable(); + } + + public Task> FindAllDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string name, @@ -94,12 +104,12 @@ public Task> FindAllDeclarationsWithNormal var result = await DeclarationFinder.FindAllDeclarationsWithNormalQueryInCurrentProcessAsync( project, query, criteria, cancellationToken).ConfigureAwait(false); - return (IList)result.SelectAsArray(SerializableSymbolAndProjectId.Dehydrate); + return Convert(result, solution, cancellationToken); } }, cancellationToken); } - public Task> FindSolutionSourceDeclarationsWithNormalQueryAsync( + public Task> FindSolutionSourceDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, string name, bool ignoreCase, @@ -114,12 +124,12 @@ public Task> FindSolutionSourceDeclaration var result = await DeclarationFinder.FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync( solution, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false); - return (IList)result.SelectAsArray(SerializableSymbolAndProjectId.Dehydrate); + return Convert(result, solution, cancellationToken); } }, cancellationToken); } - public Task> FindProjectSourceDeclarationsWithNormalQueryAsync( + public Task> FindProjectSourceDeclarationsWithNormalQueryAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string name, @@ -137,12 +147,12 @@ public Task> FindProjectSourceDeclarations var result = await DeclarationFinder.FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync( project, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false); - return (IList)result.SelectAsArray(SerializableSymbolAndProjectId.Dehydrate); + return Convert(result, solution, cancellationToken); } }, cancellationToken); } - public Task> FindSolutionSourceDeclarationsWithPatternAsync( + public Task> FindSolutionSourceDeclarationsWithPatternAsync( PinnedSolutionInfo solutionInfo, string pattern, SymbolFilter criteria, CancellationToken cancellationToken) { return RunServiceAsync(async () => @@ -154,12 +164,12 @@ public Task> FindSolutionSourceDeclaration var result = await DeclarationFinder.FindSourceDeclarationsWithPatternInCurrentProcessAsync( solution, pattern, criteria, cancellationToken).ConfigureAwait(false); - return (IList)result.SelectAsArray(SerializableSymbolAndProjectId.Dehydrate); + return Convert(result, solution, cancellationToken); } }, cancellationToken); } - public Task> FindProjectSourceDeclarationsWithPatternAsync( + public Task> FindProjectSourceDeclarationsWithPatternAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, string pattern, SymbolFilter criteria, CancellationToken cancellationToken) { return RunServiceAsync(async () => @@ -172,7 +182,7 @@ public Task> FindProjectSourceDeclarations var result = await DeclarationFinder.FindSourceDeclarationsWithPatternInCurrentProcessAsync( project, pattern, criteria, cancellationToken).ConfigureAwait(false); - return (IList)result.SelectAsArray(SerializableSymbolAndProjectId.Dehydrate); + return Convert(result, solution, cancellationToken); } }, cancellationToken); } @@ -203,13 +213,15 @@ public Task ItemCompletedAsync() private sealed class FindReferencesProgressCallback : IStreamingFindReferencesProgress, IStreamingProgressTracker { + private readonly Solution _solution; private readonly RemoteEndPoint _endPoint; private readonly CancellationToken _cancellationToken; public IStreamingProgressTracker ProgressTracker { get; } - public FindReferencesProgressCallback(RemoteEndPoint endPoint, CancellationToken cancellationToken) + public FindReferencesProgressCallback(Solution solution, RemoteEndPoint endPoint, CancellationToken cancellationToken) { + _solution = solution; _endPoint = endPoint; _cancellationToken = cancellationToken; ProgressTracker = this; @@ -228,15 +240,19 @@ public Task OnFindInDocumentCompletedAsync(Document document) => _endPoint.InvokeAsync(nameof(SymbolFinder.FindReferencesServerCallback.OnFindInDocumentCompletedAsync), new object[] { document.Id }, _cancellationToken); public Task OnDefinitionFoundAsync(SymbolAndProjectId definition) - => _endPoint.InvokeAsync(nameof(SymbolFinder.FindReferencesServerCallback.OnDefinitionFoundAsync), new object[] { SerializableSymbolAndProjectId.Dehydrate(definition) }, _cancellationToken); + => _endPoint.InvokeAsync( + nameof(SymbolFinder.FindReferencesServerCallback.OnDefinitionFoundAsync), + new object[] { SerializableSymbolAndProjectId.Dehydrate(_solution, definition.Symbol, _cancellationToken) }, _cancellationToken); public Task OnReferenceFoundAsync(SymbolAndProjectId definition, ReferenceLocation reference) - { - return _endPoint.InvokeAsync( + => _endPoint.InvokeAsync( nameof(SymbolFinder.FindReferencesServerCallback.OnReferenceFoundAsync), - new object[] { SerializableSymbolAndProjectId.Dehydrate(definition), SerializableReferenceLocation.Dehydrate(reference) }, + new object[] + { + SerializableSymbolAndProjectId.Dehydrate(_solution, definition.Symbol, _cancellationToken), + SerializableReferenceLocation.Dehydrate(reference, _cancellationToken), + }, _cancellationToken); - } public Task AddItemsAsync(int count) => _endPoint.InvokeAsync(nameof(SymbolFinder.FindReferencesServerCallback.AddItemsAsync), new object[] { count }, _cancellationToken); From 5111822f87140a230a0600e526a36124de95ce3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 15:28:33 -0700 Subject: [PATCH 02/15] Add comment --- .../SolutionState.CompilationTracker.State.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index bd32f05ce58e4..3d71889f826b5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -51,10 +51,17 @@ private class State /// /// Weak table to the assembly symbols that this compilation tracker has created. This can be used to - /// determine which project an assembly symbol came from after the fact. This is needed as the compilation - /// an assembly came from can GC'ed and further requests to get that compilation (or any of it's assemblies) - /// may produce new assembly symbols. + /// determine which project an assembly symbol came from after the fact. This is needed as the + /// compilation an assembly came from can GC'ed and further requests to get that compilation (or any of + /// it's assemblies) may produce new assembly symbols. /// + /// + /// Ideally this would just be ConditionalWeakSet<ISymbol>. Effectively we just want to + /// hold onto the symbols as long as someone else is keeping them alive. And we don't actually need + /// them to map to anything. We just use their existence to know if our project was the project it came + /// from. However, ConditionalWeakTable is the best tool we have, so we simulate a set by just using a + /// table and mapping the keys to themselves. + /// public readonly ConditionalWeakTable CompilationAssembliesAndModules; /// From 6673479a9aeb4a169710e98224ac736d35303262 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 20:43:04 -0700 Subject: [PATCH 03/15] Tweak docs --- src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index fd6d12989f91c..1d553376229d0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -127,9 +127,8 @@ private static Project CreateProject(ProjectId projectId, Solution solution) } /// - /// Given a returns the of the exact it - /// came from. This operation is not defined for s produced from other snapshots. + /// Given a returns the of the it came + /// from. Returns if does not come from . /// /// /// This function differs from in terms of how it From dea34091ebc434af166837308db536759f00f910 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 22:40:27 -0700 Subject: [PATCH 04/15] Remove unnecessary state. --- .../SolutionState.CompilationTracker.cs | 4 ---- .../SolutionState.SymbolToProjectId.cs | 24 ++++--------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 1bd9bd3a7ed18..a7fe92d1f0b81 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -744,10 +744,6 @@ private void RecordAssemblySymbols(SolutionState solutionState, Compilation comp RecordSourceOfAssemblySymbol(symbol, projectId); } - - // Record which project this compilation came from. That way we can easily map back to it when given a - // compilation for a compilation-namespace in the future. - solutionState._symbolToProjectId.RecordCompilation(compilation, this.ProjectState.Id); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs index d1df6129031aa..20d386c80fcdc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs @@ -32,16 +32,6 @@ internal partial class SolutionState /// private struct SymbolToProjectId { - /// - /// Weak cache from a to the ID of the it was created from. - /// Only used for mapping a 's back. These - /// 's do not belong to a or and thus cannot be tracked with . - /// - private static readonly ConditionalWeakTable s_compilationToProjectId - = new ConditionalWeakTable(); - private readonly SolutionState _solutionState; /// @@ -85,10 +75,10 @@ public SymbolToProjectId(SolutionState solutionState) { if (ns.ContainingCompilation != null) { - // A namespace that spans a compilation. These don't belong to an assembly/module. However, we - // can map their compilation directly - s_compilationToProjectId.TryGetValue(ns.ContainingCompilation, out var projectId); - return projectId; + // A namespace that spans a compilation. These don't belong to an assembly/module directly. + // However, as we're looking for the project this corresponds to, we can look for the + // source-module component (the first in the constituent namespaces) and then search using that. + return GetExactProjectId(ns.ConstituentNamespaces[0]); } } else if (symbol.IsKind(SymbolKind.Assembly) || @@ -124,12 +114,6 @@ public SymbolToProjectId(SolutionState solutionState) return null; } - - public void RecordCompilation(Compilation compilation, ProjectId id) - { - if (!s_compilationToProjectId.TryGetValue(compilation, out _)) - s_compilationToProjectId.Add(compilation, id); - } } } } From feba5151018dce7124ebc950ae71dd5f608402da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 22:49:40 -0700 Subject: [PATCH 05/15] Simplify --- .../SolutionState.CompilationTracker.cs | 6 +- .../SolutionState.SymbolToProjectId.cs | 121 ++++++------------ .../Workspace/Solution/SolutionState.cs | 11 +- 3 files changed, 54 insertions(+), 84 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index a7fe92d1f0b81..23d0ed9454564 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -311,7 +311,7 @@ private void GetPartialCompilationState( inProgressCompilation = inProgressCompilation.WithReferences(metadataReferences); } - RecordAssemblySymbols(solution, inProgressCompilation, metadataReferenceToProjectId); + RecordAssemblySymbols(inProgressCompilation, metadataReferenceToProjectId); SolutionLogger.CreatePartialProjectState(); } @@ -710,7 +710,7 @@ private async Task FinalizeCompilationAsync( generatorDriver = new TrackedGeneratorDriver(generatorDriver.GeneratorDriver.RunFullGeneration(compilation, out compilation, out var diagnostics, cancellationToken)); } - RecordAssemblySymbols(solution, compilation, metadataReferenceToProjectId); + RecordAssemblySymbols(compilation, metadataReferenceToProjectId); this.WriteState( new FinalState( @@ -730,7 +730,7 @@ private async Task FinalizeCompilationAsync( } } - private void RecordAssemblySymbols(SolutionState solutionState, Compilation compilation, Dictionary metadataReferenceToProjectId) + private void RecordAssemblySymbols(Compilation compilation, Dictionary metadataReferenceToProjectId) { // TODO: Record source assembly to project mapping // RecordSourceOfAssemblySymbol(compilation.Assembly, this.ProjectState.Id); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs index 20d386c80fcdc..4e0aaf44eb872 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs @@ -4,7 +4,6 @@ #nullable enable -using System; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -13,107 +12,71 @@ namespace Microsoft.CodeAnalysis { internal partial class SolutionState { - /// - /// Contains caches to help us quickly and accurately map from s to . - /// - /// - /// Note: this is not as this is a struct and will mutate as operations are called on - /// it. However, in the common case where this data is not needed, it will have not have any impact. - /// - private SymbolToProjectId _symbolToProjectId; - /// - public ProjectId? GetExactProjectId(ISymbol symbol) - => _symbolToProjectId.GetExactProjectId(symbol); - - /// - /// A helper type that can be used to map from symbols to a project they could have come from. - /// - private struct SymbolToProjectId + public ProjectId? GetExactProjectId(ISymbol? symbol) { - private readonly SolutionState _solutionState; + LazyInitialization.EnsureInitialized(ref _assemblyOrModuleSymbolToProjectId, s_createTable); - /// - /// Cache we use to map between assembly and module symbols and the project they came from. That way if we - /// are asked about many symbols from the same assembly/module we can answer the question quickly after - /// computing for the first one. - /// - private ConditionalWeakTable? _assemblyOrModuleSymbolToProjectId; - private static readonly Func> s_createTable = () => new ConditionalWeakTable(); + // Walk up the symbol so we can get to the containing namespace/assembly that will be used to map + // back to a project. - public SymbolToProjectId(SolutionState solutionState) + while (symbol != null) { - _solutionState = solutionState; - _assemblyOrModuleSymbolToProjectId = null; - } + var result = GetProjectIdDirectly(symbol, _assemblyOrModuleSymbolToProjectId); + if (result != null) + return result; - /// . - public ProjectId? GetExactProjectId(ISymbol? symbol) - { - LazyInitialization.EnsureInitialized(ref _assemblyOrModuleSymbolToProjectId, s_createTable); + symbol = symbol.ContainingSymbol; + } - // Walk up the symbol so we can get to the containing namespace/assembly that will be used to map - // back to a project. + return null; + } - while (symbol != null) + private ProjectId? GetProjectIdDirectly( + ISymbol symbol, ConditionalWeakTable assemblyOrModuleSymbolToProjectId) + { + if (symbol.IsKind(SymbolKind.Namespace, out INamespaceSymbol? ns)) + { + if (ns.ContainingCompilation != null) { - var result = GetProjectIdDirectly(symbol, _assemblyOrModuleSymbolToProjectId); - if (result != null) - return result; - - symbol = symbol.ContainingSymbol; + // A namespace that spans a compilation. These don't belong to an assembly/module directly. + // However, as we're looking for the project this corresponds to, we can look for the + // source-module component (the first in the constituent namespaces) and then search using that. + return GetExactProjectId(ns.ConstituentNamespaces[0]); } - - return null; } - - private ProjectId? GetProjectIdDirectly( - ISymbol symbol, ConditionalWeakTable assemblyOrModuleSymbolToProjectId) + else if (symbol.IsKind(SymbolKind.Assembly) || + symbol.IsKind(SymbolKind.NetModule)) { - if (symbol.IsKind(SymbolKind.Namespace, out INamespaceSymbol? ns)) + if (!assemblyOrModuleSymbolToProjectId.TryGetValue(symbol, out var projectId)) { - if (ns.ContainingCompilation != null) + foreach (var (id, state) in this.ProjectStates) { - // A namespace that spans a compilation. These don't belong to an assembly/module directly. - // However, as we're looking for the project this corresponds to, we can look for the - // source-module component (the first in the constituent namespaces) and then search using that. - return GetExactProjectId(ns.ConstituentNamespaces[0]); - } - } - else if (symbol.IsKind(SymbolKind.Assembly) || - symbol.IsKind(SymbolKind.NetModule)) - { - if (!assemblyOrModuleSymbolToProjectId.TryGetValue(symbol, out var projectId)) - { - foreach (var (id, state) in _solutionState.ProjectStates) + var tracker = this.GetCompilationTracker(id); + if (tracker.ContainsAssemblyOrModule(symbol)) { - var tracker = _solutionState.GetCompilationTracker(id); - if (tracker.ContainsAssemblyOrModule(symbol)) - { - projectId = id; - break; - } + projectId = id; + break; } + } - // Have to lock as there's no atomic AddOrUpdate in netstandard2.0 and we could throw if two - // threads tried to add the same item. + // Have to lock as there's no atomic AddOrUpdate in netstandard2.0 and we could throw if two + // threads tried to add the same item. #if NETSTANDARD - lock (assemblyOrModuleSymbolToProjectId) - { - assemblyOrModuleSymbolToProjectId.Remove(symbol); - assemblyOrModuleSymbolToProjectId.Add(symbol, projectId); - } + lock (assemblyOrModuleSymbolToProjectId) + { + assemblyOrModuleSymbolToProjectId.Remove(symbol); + assemblyOrModuleSymbolToProjectId.Add(symbol, projectId); + } #else - assemblyOrModuleSymbolToProjectId.AddOrUpdate(symbol, projectId); + assemblyOrModuleSymbolToProjectId.AddOrUpdate(symbol, projectId); #endif - } - - return projectId; } - return null; + return projectId; } + + return null; } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 34866f7753f44..c32db72740d55 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -51,6 +51,15 @@ internal partial class SolutionState // Checksums for this solution state private readonly ValueSource _lazyChecksums; + /// + /// Cache we use to map between assembly and module symbols and the project they came from. That way if we + /// are asked about many symbols from the same assembly/module we can answer the question quickly after + /// computing for the first one. Created on demand. + /// + private ConditionalWeakTable? _assemblyOrModuleSymbolToProjectId; + private static readonly Func> s_createTable = () => new ConditionalWeakTable(); + + private SolutionState( BranchId branchId, int workspaceVersion, @@ -74,8 +83,6 @@ private SolutionState( _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; - _symbolToProjectId = new SymbolToProjectId(this); - // when solution state is changed, we re-calcuate its checksum _lazyChecksums = new AsyncLazy(ComputeChecksumsAsync, cacheResult: true); From a2964ff28078a85cfe80888c0ebea9a7cbb82a44 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 22:53:40 -0700 Subject: [PATCH 06/15] Only iterate projects with compilation trakers. --- .../Workspace/Solution/SolutionState.SymbolToProjectId.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs index 4e0aaf44eb872..c0f9834ef284f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs @@ -50,9 +50,8 @@ internal partial class SolutionState { if (!assemblyOrModuleSymbolToProjectId.TryGetValue(symbol, out var projectId)) { - foreach (var (id, state) in this.ProjectStates) + foreach (var (id, tracker) in _projectIdToTrackerMap) { - var tracker = this.GetCompilationTracker(id); if (tracker.ContainsAssemblyOrModule(symbol)) { projectId = id; From 7075780f2bbb68f0b924d2d85ed9108ccbebd0cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 9 Apr 2020 23:05:51 -0700 Subject: [PATCH 07/15] Rename for clarity --- .../SolutionState.CompilationTracker.State.cs | 33 ++++++++++--------- .../SolutionState.CompilationTracker.cs | 7 ++-- .../Workspace/Solution/SolutionState.cs | 1 - 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index 3d71889f826b5..82418d71174c0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -33,7 +33,8 @@ private class State /// public static readonly State Empty = new State( compilation: null, declarationOnlyCompilation: null, - generatorDriver: new TrackedGeneratorDriver(null), new ConditionalWeakTable()); + generatorDriver: new TrackedGeneratorDriver(null), + assemblyAndModuleSet: null); /// /// A strong reference to the declaration-only compilation. This compilation isn't used to produce symbols, @@ -50,19 +51,19 @@ private class State public TrackedGeneratorDriver GeneratorDriver { get; } /// - /// Weak table to the assembly symbols that this compilation tracker has created. This can be used to - /// determine which project an assembly symbol came from after the fact. This is needed as the - /// compilation an assembly came from can GC'ed and further requests to get that compilation (or any of - /// it's assemblies) may produce new assembly symbols. + /// Weak table to the assembly and module symbols that this compilation tracker has created. This can + /// be used to determine which project an assembly symbol came from after the fact. This is needed as + /// the compilation an assembly came from can GC'ed and further requests to get that compilation (or any + /// of it's assemblies) may produce new assembly symbols. /// /// /// Ideally this would just be ConditionalWeakSet<ISymbol>. Effectively we just want to /// hold onto the symbols as long as someone else is keeping them alive. And we don't actually need /// them to map to anything. We just use their existence to know if our project was the project it came /// from. However, ConditionalWeakTable is the best tool we have, so we simulate a set by just using a - /// table and mapping the keys to themselves. + /// table and mapping the keys to the value. /// - public readonly ConditionalWeakTable CompilationAssembliesAndModules; + public readonly ConditionalWeakTable? AssemblyAndModuleSet; /// /// Specifies whether and all compilations it depends on contain full information or not. This can return @@ -79,7 +80,7 @@ protected State( ValueSource>? compilation, Compilation? declarationOnlyCompilation, TrackedGeneratorDriver generatorDriver, - ConditionalWeakTable compilationAssembliesAndModules) + ConditionalWeakTable? assemblyAndModuleSet) { // Declaration-only compilations should never have any references Contract.ThrowIfTrue(declarationOnlyCompilation != null && declarationOnlyCompilation.ExternalReferences.Any()); @@ -87,7 +88,7 @@ protected State( Compilation = compilation; DeclarationOnlyCompilation = declarationOnlyCompilation; GeneratorDriver = generatorDriver; - CompilationAssembliesAndModules = compilationAssembliesAndModules; + AssemblyAndModuleSet = assemblyAndModuleSet; } public static State Create( @@ -114,9 +115,9 @@ public static ValueSource> CreateValueSource( : (ValueSource>)new ConstantValueSource>(compilation); } - public static ConditionalWeakTable GetCompilationAssembliesAndModules(Compilation compilation) + public static ConditionalWeakTable GetAssemblyAndModuleSet(Compilation compilation) { - var result = new ConditionalWeakTable(); + var result = new ConditionalWeakTable(); var compAssembly = compilation.Assembly; result.Add(compAssembly, compAssembly); @@ -127,7 +128,7 @@ public static ConditionalWeakTable GetCompilationAssembliesAnd if (symbol == null) continue; - result.Add(symbol, symbol); + result.Add(symbol, null); } return result; @@ -149,7 +150,7 @@ public InProgressState( : base(compilation: new ConstantValueSource>(inProgressCompilation), declarationOnlyCompilation: null, generatorDriver: inProgressGeneratorDriver, - GetCompilationAssembliesAndModules(inProgressCompilation)) + GetAssemblyAndModuleSet(inProgressCompilation)) { Contract.ThrowIfTrue(intermediateProjects.IsDefault); Contract.ThrowIfFalse(intermediateProjects.Length > 0); @@ -167,7 +168,7 @@ public LightDeclarationState(Compilation declarationOnlyCompilation) : base(compilation: null, declarationOnlyCompilation: declarationOnlyCompilation, generatorDriver: new TrackedGeneratorDriver(null), - new ConditionalWeakTable()) + assemblyAndModuleSet: null) { } } @@ -182,7 +183,7 @@ public FullDeclarationState(Compilation declarationCompilation, TrackedGenerator : base(new WeakValueSource(declarationCompilation), declarationCompilation.Clone().RemoveAllReferences(), generatorDriver, - GetCompilationAssembliesAndModules(declarationCompilation)) + GetAssemblyAndModuleSet(declarationCompilation)) { } } @@ -210,7 +211,7 @@ public FinalState( Compilation compilationWithoutGeneratedFiles, TrackedGeneratorDriver generatorDriver, bool hasSuccessfullyLoaded, - ConditionalWeakTable compilationAssemblies) + ConditionalWeakTable? compilationAssemblies) : base(compilationWithoutGeneratedFilesSource, compilationWithoutGeneratedFiles.Clone().RemoveAllReferences(), generatorDriver, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 23d0ed9454564..eef1bcf4281c0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -104,7 +104,8 @@ public bool ContainsAssemblyOrModule(ISymbol assemblyOrModule) { Debug.Assert(assemblyOrModule.Kind == SymbolKind.Assembly || assemblyOrModule.Kind == SymbolKind.NetModule); var state = this.ReadState(); - return state.CompilationAssembliesAndModules.TryGetValue(assemblyOrModule, out _); + var assemblyAndModuleSet = state.AssemblyAndModuleSet; + return assemblyAndModuleSet != null && assemblyAndModuleSet.TryGetValue(assemblyOrModule, out _); } /// @@ -196,7 +197,7 @@ public CompilationTracker FreezePartialStateWithTree(SolutionState solution, Doc inProgressCompilation, generatorDriver: new TrackedGeneratorDriver(null), hasSuccessfullyLoaded: false, - State.GetCompilationAssembliesAndModules(inProgressCompilation))); + State.GetAssemblyAndModuleSet(inProgressCompilation))); } /// @@ -719,7 +720,7 @@ private async Task FinalizeCompilationAsync( compilationWithoutGeneratedFiles, generatorDriver, hasSuccessfullyLoaded, - State.GetCompilationAssembliesAndModules(compilation)), + State.GetAssemblyAndModuleSet(compilation)), solution.Services); return new CompilationInfo(compilation, hasSuccessfullyLoaded); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index c32db72740d55..67ffc73ec7f0c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -59,7 +59,6 @@ internal partial class SolutionState private ConditionalWeakTable? _assemblyOrModuleSymbolToProjectId; private static readonly Func> s_createTable = () => new ConditionalWeakTable(); - private SolutionState( BranchId branchId, int workspaceVersion, From 23a72b2d3b6d7b91f5ed3c47941b6b16ba19ba94 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 14 Apr 2020 11:04:48 -0700 Subject: [PATCH 08/15] Remove default hashing/equality --- ...ymbolFinder.FindReferencesServerCallback.cs | 18 +++++++++++++++--- .../Core/Portable/Remote/RemoteArguments.cs | 18 +----------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 6fe716fd5772d..b7ae067e29832 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -15,15 +15,20 @@ public static partial class SymbolFinder /// Callback object we pass to the OOP server to hear about the result /// of the FindReferencesEngine as it executes there. /// - internal sealed class FindReferencesServerCallback + internal sealed class FindReferencesServerCallback : IEqualityComparer { private readonly Solution _solution; private readonly IStreamingFindReferencesProgress _progress; private readonly CancellationToken _cancellationToken; private readonly object _gate = new object(); - private readonly Dictionary _definitionMap = - new Dictionary(); + + /// + /// Note: for purposes of Equality/Hashing, all that we use is the underlying SymbolKey. That's because FAR + /// only cares if it is looking at the same symbol, it don't care if the symbol came from a different + /// project or not. + /// + private readonly Dictionary _definitionMap; public FindReferencesServerCallback( Solution solution, @@ -33,6 +38,7 @@ public FindReferencesServerCallback( _solution = solution; _progress = progress; _cancellationToken = cancellationToken; + _definitionMap = new Dictionary(this); } public Task AddItemsAsync(int count) => _progress.ProgressTracker.AddItemsAsync(count); @@ -85,6 +91,12 @@ public async Task OnReferenceFoundAsync( await _progress.OnReferenceFoundAsync(symbolAndProjectId, referenceLocation).ConfigureAwait(false); } + + bool IEqualityComparer.Equals(SerializableSymbolAndProjectId x, SerializableSymbolAndProjectId y) + => y.SymbolKeyData.Equals(x.SymbolKeyData); + + int IEqualityComparer.GetHashCode(SerializableSymbolAndProjectId obj) + => obj.SymbolKeyData.GetHashCode(); } } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index f42660ecd10d2..f4e158212231e 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -37,27 +37,11 @@ public FindReferencesSearchOptions Rehydrate() } } - /// - /// Note: for purposes of Equality/Hashing, all that we use is the underlying SymbolKey. That's because nearly all - /// IDE features only care if they're looking at the same symbol, they don't care if the symbol came from a - /// different project or not. i.e. a feature like FAR wants to bucket all references to the 'same' originating - /// symbol even if one reference is to a re-targeted version of that symbol. As such, we do not include the - /// ProjectId when computing the result. - /// - internal class SerializableSymbolAndProjectId : IEquatable + internal class SerializableSymbolAndProjectId { public string SymbolKeyData; public ProjectId ProjectId; - public override int GetHashCode() - => SymbolKeyData.GetHashCode(); - - public override bool Equals(object obj) - => Equals(obj as SerializableSymbolAndProjectId); - - public bool Equals(SerializableSymbolAndProjectId other) - => other != null && SymbolKeyData.Equals(other.SymbolKeyData); - public static SerializableSymbolAndProjectId Dehydrate( IAliasSymbol alias, Document document, CancellationToken cancellationToken) { From 34b4273e778ae2985975f9a7fc836f8cb53f3a13 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 14 Apr 2020 11:28:00 -0700 Subject: [PATCH 09/15] Update src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs --- .../Solution/SolutionState.CompilationTracker.State.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index 82418d71174c0..af5edb897e677 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -120,7 +120,7 @@ public static ValueSource> CreateValueSource( var result = new ConditionalWeakTable(); var compAssembly = compilation.Assembly; - result.Add(compAssembly, compAssembly); + result.Add(compAssembly, null); foreach (var reference in compilation.References) { From a88e60a303690d2bbaed215b2f4423817b3b5e7c Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 16 Apr 2020 09:41:23 -0700 Subject: [PATCH 10/15] Avoid sharing ReferenceManager across script compilations that do not share previous submission. --- .../Portable/Compilation/CSharpCompilation.cs | 14 +++++-- .../Symbol/Compilation/CompilationAPITests.cs | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 00e2e696737be..e243ab285b400 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -338,6 +338,7 @@ private static CSharpCompilation Create( // // TODO: Consider reusing some results of the assembly binding to improve perf // since most of the binding work is similar. + // https://github.com/dotnet/roslyn/issues/43397 var compilation = new CSharpCompilation( assemblyName, @@ -609,7 +610,13 @@ public CSharpCompilation WithScriptCompilationInfo(CSharpScriptCompilationInfo? return this; } - // Reference binding doesn't depend on previous submission so we can reuse it. + // Metadata references are inherited from the previous submission, + // so we can only reuse the manager if we can guarantee that these references are the same. + // Check if the previous script compilation doesn't change. + + // TODO: Consider comparing the metadata references if they have been bound already. + // https://github.com/dotnet/roslyn/issues/43397 + bool reuseReferenceManager = ReferenceEquals(ScriptCompilationInfo?.PreviousScriptCompilation, info?.PreviousScriptCompilation); return new CSharpCompilation( this.AssemblyName, @@ -618,9 +625,9 @@ public CSharpCompilation WithScriptCompilationInfo(CSharpScriptCompilationInfo? info?.PreviousScriptCompilation, info?.ReturnTypeOpt, info?.GlobalsType, - info != null, + isSubmission: info != null, _referenceManager, - reuseReferenceManager: true, + reuseReferenceManager, syntaxAndDeclarations: _syntaxAndDeclarations); } @@ -912,6 +919,7 @@ internal override bool HasSubmissionResult() // TODO(tomat): Consider comparing #r's of the old and the new tree. If they are exactly the same we could still reuse. // This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke // that replaces the tree with a new one. + // https://github.com/dotnet/roslyn/issues/43397 var reuseReferenceManager = !oldTree.HasReferenceOrLoadDirectives() && !newTree.HasReferenceOrLoadDirectives(); syntaxAndDeclarations = syntaxAndDeclarations.ReplaceSyntaxTree(oldTree, newTree); diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs index 43cf3e7753a78..e1bc08661c9bc 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs @@ -2076,6 +2076,44 @@ class C { }", options: TestOptions.Script); Assert.False(arc.ReferenceManagerEquals(ars)); } + [Fact] + public void ReferenceManagerReuse_WithScriptCompilationInfo() + { + // Note: The following results would change if we optimized sharing more: https://github.com/dotnet/roslyn/issues/43397 + + var c1 = CSharpCompilation.CreateScriptCompilation("c1"); + Assert.NotNull(c1.ScriptCompilationInfo); + Assert.Null(c1.ScriptCompilationInfo.PreviousScriptCompilation); + + var c2 = c1.WithScriptCompilationInfo(null); + Assert.Null(c2.ScriptCompilationInfo); + Assert.True(c2.ReferenceManagerEquals(c1)); + + var c3 = c2.WithScriptCompilationInfo(new CSharpScriptCompilationInfo(previousCompilationOpt: null, returnType: typeof(int), globalsType: null)); + Assert.NotNull(c3.ScriptCompilationInfo); + Assert.Null(c3.ScriptCompilationInfo.PreviousScriptCompilation); + Assert.True(c3.ReferenceManagerEquals(c2)); + + var c4 = c3.WithScriptCompilationInfo(null); + Assert.Null(c4.ScriptCompilationInfo); + Assert.True(c4.ReferenceManagerEquals(c3)); + + var c5 = c4.WithScriptCompilationInfo(new CSharpScriptCompilationInfo(previousCompilationOpt: c1, returnType: typeof(int), globalsType: null)); + Assert.False(c5.ReferenceManagerEquals(c4)); + + var c6 = c5.WithScriptCompilationInfo(new CSharpScriptCompilationInfo(previousCompilationOpt: c1, returnType: typeof(bool), globalsType: null)); + Assert.True(c6.ReferenceManagerEquals(c5)); + + var c7 = c6.WithScriptCompilationInfo(new CSharpScriptCompilationInfo(previousCompilationOpt: c2, returnType: typeof(bool), globalsType: null)); + Assert.False(c7.ReferenceManagerEquals(c6)); + + var c8 = c7.WithScriptCompilationInfo(new CSharpScriptCompilationInfo(previousCompilationOpt: null, returnType: typeof(bool), globalsType: null)); + Assert.False(c8.ReferenceManagerEquals(c7)); + + var c9 = c8.WithScriptCompilationInfo(null); + Assert.True(c9.ReferenceManagerEquals(c8)); + } + private sealed class EvolvingTestReference : PortableExecutableReference { private readonly IEnumerator _metadataSequence; From f2da910284db0b00bf0ae31ae2f5de1d8e4fc4e9 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Fri, 17 Apr 2020 15:59:41 -0700 Subject: [PATCH 11/15] VB --- .../Compilation/VisualBasicCompilation.vb | 10 ++++- .../Compilation/CompilationAPITests.vb | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index 6375aafaa95a6..57251aea218c3 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -661,7 +661,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return Me End If - ' Reference binding doesn't depend on previous submission so we can reuse it. + ' Metadata references are inherited from the previous submission, + ' so we can only reuse the manager if we can guarantee that these references are the same. + ' Check if the previous script compilation doesn't change. + + ' TODO Consider comparing the metadata references if they have been bound already. + ' https://github.com/dotnet/roslyn/issues/43397 + Dim reuseReferenceManager = ReferenceEquals(ScriptCompilationInfo?.PreviousScriptCompilation, info?.PreviousScriptCompilation) Return New VisualBasicCompilation( Me.AssemblyName, @@ -677,7 +683,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic info?.GlobalsType, info IsNot Nothing, _referenceManager, - reuseReferenceManager:=True) + reuseReferenceManager) End Function ''' diff --git a/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb b/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb index a93ed6e257130..e28deda4313fb 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb @@ -2176,6 +2176,43 @@ End Class Assert.False(arc.ReferenceManagerEquals(ars)) End Sub + + Public Sub ReferenceManagerReuse_WithScriptCompilationInfo() + ' Note The following results would change if we optimized sharing more: https://github.com/dotnet/roslyn/issues/43397 + + Dim c1 = VisualBasicCompilation.CreateScriptCompilation("c1") + Assert.NotNull(c1.ScriptCompilationInfo) + Assert.Null(c1.ScriptCompilationInfo.PreviousScriptCompilation) + + Dim c2 = c1.WithScriptCompilationInfo(Nothing) + Assert.Null(c2.ScriptCompilationInfo) + Assert.True(c2.ReferenceManagerEquals(c1)) + + Dim c3 = c2.WithScriptCompilationInfo(New VisualBasicScriptCompilationInfo(previousCompilationOpt:=Nothing, returnType:=GetType(Integer), globalsType:=Nothing)) + Assert.NotNull(c3.ScriptCompilationInfo) + Assert.Null(c3.ScriptCompilationInfo.PreviousScriptCompilation) + Assert.True(c3.ReferenceManagerEquals(c2)) + + Dim c4 = c3.WithScriptCompilationInfo(Nothing) + Assert.Null(c4.ScriptCompilationInfo) + Assert.True(c4.ReferenceManagerEquals(c3)) + + Dim c5 = c4.WithScriptCompilationInfo(New VisualBasicScriptCompilationInfo(previousCompilationOpt:=c1, returnType:=GetType(Integer), globalsType:=Nothing)) + Assert.False(c5.ReferenceManagerEquals(c4)) + + Dim c6 = c5.WithScriptCompilationInfo(New VisualBasicScriptCompilationInfo(previousCompilationOpt:=c1, returnType:=GetType(Boolean), globalsType:=Nothing)) + Assert.True(c6.ReferenceManagerEquals(c5)) + + Dim c7 = c6.WithScriptCompilationInfo(New VisualBasicScriptCompilationInfo(previousCompilationOpt:=c2, returnType:=GetType(Boolean), globalsType:=Nothing)) + Assert.False(c7.ReferenceManagerEquals(c6)) + + Dim c8 = c7.WithScriptCompilationInfo(New VisualBasicScriptCompilationInfo(previousCompilationOpt:=Nothing, returnType:=GetType(Boolean), globalsType:=Nothing)) + Assert.False(c8.ReferenceManagerEquals(c7)) + + Dim c9 = c8.WithScriptCompilationInfo(Nothing) + Assert.True(c9.ReferenceManagerEquals(c8)) + End Sub + Private Class EvolvingTestReference Inherits PortableExecutableReference From 4f979ca5314c080c87371b4fe8a325a5dbac5cac Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Sat, 18 Apr 2020 10:40:42 -0700 Subject: [PATCH 12/15] Fix --- .../Portable/Compilation/VisualBasicCompilation.vb | 2 +- .../Test/Semantic/Compilation/CompilationAPITests.vb | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index 57251aea218c3..fac7ef2351417 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -667,7 +667,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' TODO Consider comparing the metadata references if they have been bound already. ' https://github.com/dotnet/roslyn/issues/43397 - Dim reuseReferenceManager = ReferenceEquals(ScriptCompilationInfo?.PreviousScriptCompilation, info?.PreviousScriptCompilation) + Dim reuseReferenceManager = ScriptCompilationInfo?.PreviousScriptCompilation Is info?.PreviousScriptCompilation Return New VisualBasicCompilation( Me.AssemblyName, diff --git a/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb b/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb index e28deda4313fb..440f29e54b1f2 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Compilation/CompilationAPITests.vb @@ -2049,15 +2049,6 @@ End Class Assert.False(c1.ReferenceManagerEquals(c2)) End Sub - - Public Sub ReferenceManagerReuse_WithPreviousSubmission() - Dim s1 = VisualBasicCompilation.CreateScriptCompilation("s1") - Dim s2 = VisualBasicCompilation.CreateScriptCompilation("s2") - - Dim s3 = s2.WithScriptCompilationInfo(s2.ScriptCompilationInfo.WithPreviousScriptCompilation(s1)) - Assert.True(s2.ReferenceManagerEquals(s3)) - End Sub - Public Sub ReferenceManagerReuse_WithXmlFileResolver() Dim c1 = VisualBasicCompilation.Create("c", options:=TestOptions.ReleaseDll) From 863593d0c2561b7ac817b76260e5d9500a4d6cc4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Apr 2020 12:02:46 -0700 Subject: [PATCH 13/15] Tweak grammar --- .../Solution/SolutionState.CompilationTracker.State.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index af5edb897e677..e64f486eccd54 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -51,7 +51,7 @@ private class State public TrackedGeneratorDriver GeneratorDriver { get; } /// - /// Weak table to the assembly and module symbols that this compilation tracker has created. This can + /// Weak table of the assembly and module symbols that this compilation tracker has created. This can /// be used to determine which project an assembly symbol came from after the fact. This is needed as /// the compilation an assembly came from can GC'ed and further requests to get that compilation (or any /// of it's assemblies) may produce new assembly symbols. From f1de8bffbef323aa378ec5ef0fe06db39872687b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Apr 2020 12:03:05 -0700 Subject: [PATCH 14/15] Tweak grammar --- .../Solution/SolutionState.CompilationTracker.State.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs index e64f486eccd54..d98698cc435e4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs @@ -53,8 +53,8 @@ private class State /// /// Weak table of the assembly and module symbols that this compilation tracker has created. This can /// be used to determine which project an assembly symbol came from after the fact. This is needed as - /// the compilation an assembly came from can GC'ed and further requests to get that compilation (or any - /// of it's assemblies) may produce new assembly symbols. + /// the compilation an assembly came from can be GC'ed and further requests to get that compilation (or + /// any of it's assemblies) may produce new assembly symbols. /// /// /// Ideally this would just be ConditionalWeakSet<ISymbol>. Effectively we just want to From 9d1bd7c7f7373859dfe36815ce23eee75de99352 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Apr 2020 12:10:47 -0700 Subject: [PATCH 15/15] Use resource --- src/Workspaces/Core/Portable/Remote/RemoteArguments.cs | 3 +-- src/Workspaces/Core/Portable/WorkspacesResources.resx | 3 +++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf | 5 +++++ .../Core/Portable/xlf/WorkspacesResources.pt-BR.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf | 5 +++++ src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf | 5 +++++ .../Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf | 5 +++++ .../Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf | 5 +++++ 15 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index f4e158212231e..9a1b616882dfe 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -55,8 +55,7 @@ public static SerializableSymbolAndProjectId Dehydrate( { var symbolKey = symbol.GetSymbolKey(cancellationToken); var projectId = solution.GetExactProjectId(symbol); - if (projectId == null) - throw new ArgumentException("Symbol's project could not be found in the Solution provided"); + Contract.ThrowIfNull(projectId, WorkspacesResources.Symbols_project_could_not_be_found_in_the_provided_solution); return new SerializableSymbolAndProjectId { diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx index 1dde52b6f9d25..39e05a55ac404 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -553,4 +553,7 @@ Unknown + + Symbol's project could not be found in the provided solution + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 927aed0ecac19..cd1c8908e9287 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -172,6 +172,11 @@ Přidávání projektů se nepodporuje. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index 8edd7c4742e15..64377f1c43998 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -172,6 +172,11 @@ Das Hinzufügen von Projekten wird nicht unterstützt. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index 10a857869d5d1..90b817c0b25bf 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -172,6 +172,11 @@ No se admite la adición de proyectos. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 1e9a44690981e..9a7247c1d20c3 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -172,6 +172,11 @@ L'ajout de projets n'est pas pris en charge. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index e390333bf5a71..96ec6d11bc0df 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -172,6 +172,11 @@ L'aggiunta di progetti non è supportata. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 3b3ff9d7ec695..20b168f0c7d18 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -172,6 +172,11 @@ プロジェクトの追加はサポートされていません。 + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index f82e78f4a7042..791183aadef82 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -172,6 +172,11 @@ 프로젝트 추가가 지원되지 않습니다. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index ce51a1359c4fc..c0817d3f8b274 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -172,6 +172,11 @@ Dodawanie projektów nie jest obsługiwane. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index e97d1c1ff8249..593d1ff0828c0 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -172,6 +172,11 @@ Não há suporte para adicionar projetos. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index e1a5d5082256e..757fa537a55c1 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -172,6 +172,11 @@ Добавление проектов не поддерживается. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index 65b15ef347614..980ccc20171ed 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -172,6 +172,11 @@ Projelerin eklenmesi desteklenmiyor. + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 974afac279fcd..700d7440735dc 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -172,6 +172,11 @@ 不支持添加项目。 + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index 97722d4bec82c..b097edeb49440 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -172,6 +172,11 @@ 不支援新增專案。 + + Symbol's project could not be found in the provided solution + Symbol's project could not be found in the provided solution + + The project already contains the specified reference. The project already contains the specified reference.