File: Workspace\Solution\SolutionState.UnrootedSymbolSet.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
 
namespace Microsoft.CodeAnalysis
{
    internal partial class SolutionState
    {
        /// <summary>
        /// A helper type for mapping <see cref="ISymbol"/> back to an originating <see cref="Project"/>.
        /// </summary>
        /// <remarks>
        /// In IDE scenarios we have the need to map from an <see cref="ISymbol"/> to the <see cref="Project"/> that
        /// contained a <see cref="Compilation"/> that could have produced that symbol.  This is especially needed with
        /// OOP scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to.
        /// To do this, we pass along a project where this symbol could be found, and enough information (a <see
        /// cref="SymbolKey"/>) to resolve that symbol back in that that <see cref="Project"/>.
        /// <para>
        /// This is challenging however as symbols do not necessarily have back-pointers to <see cref="Compilation"/>s,
        /// and as such, we can't just see which Project produced the <see cref="Compilation"/> that produced that <see
        /// cref="ISymbol"/>.  In other words, the <see cref="ISymbol"/> doesn't <c>root</c> the compilation.  Because
        /// of that we keep track of those symbols per project in a <em>weak</em> fashion.  Then, we can later see if a
        /// symbol came from a particular project by checking if it is one of those weak symbols.  We use weakly held
        /// symbols to that a <see cref="ProjectState"/> instance doesn't hold symbols alive.  But, we know if we are
        /// holding the symbol itself, then the weak-ref will stay alive such that we can do this containment check.
        /// </para>
        /// </remarks>
        private readonly struct UnrootedSymbolSet
        {
            /// <summary>
            /// The <see cref="IAssemblySymbol"/> produced directly by <see cref="Compilation.Assembly"/>.
            /// </summary>
            public readonly WeakReference<IAssemblySymbol> PrimaryAssemblySymbol;
 
            /// <summary>
            /// The <see cref="IDynamicTypeSymbol"/> produced directly by <see cref="Compilation.DynamicType"/>.  Only
            /// valid for <see cref="LanguageNames.CSharp"/>.
            /// </summary>
            public readonly WeakReference<ITypeSymbol?> PrimaryDynamicSymbol;
 
            /// <summary>
            /// The <see cref="IAssemblySymbol"/>s or <see cref="IModuleSymbol"/>s produced through <see
            /// cref="Compilation.GetAssemblyOrModuleSymbol(MetadataReference)"/> for all the references exposed by <see
            /// cref="Compilation.References"/>.  Sorted by the hash code produced by <see
            /// cref="ReferenceEqualityComparer.GetHashCode(object?)"/> so that it can be binary searched efficiently.
            /// </summary>
            public readonly ImmutableArray<(int hashCode, WeakReference<ISymbol> symbol)> SecondaryReferencedSymbols;
 
            private UnrootedSymbolSet(
                WeakReference<IAssemblySymbol> primaryAssemblySymbol,
                WeakReference<ITypeSymbol?> primaryDynamicSymbol,
                ImmutableArray<(int hashCode, WeakReference<ISymbol> symbol)> secondaryReferencedSymbols)
            {
                PrimaryAssemblySymbol = primaryAssemblySymbol;
                PrimaryDynamicSymbol = primaryDynamicSymbol;
                SecondaryReferencedSymbols = secondaryReferencedSymbols;
            }
 
            public static UnrootedSymbolSet Create(Compilation compilation)
            {
                var primaryAssembly = new WeakReference<IAssemblySymbol>(compilation.Assembly);
 
                // The dynamic type is also unrooted (i.e. doesn't point back at the compilation or source
                // assembly).  So we have to keep track of it so we can get back from it to a project in case the 
                // underlying compilation is GC'ed.
                var primaryDynamic = new WeakReference<ITypeSymbol?>(
                    compilation.Language == LanguageNames.CSharp ? compilation.DynamicType : null);
 
                // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols.
                using var _ = ArrayBuilder<(int hashcode, WeakReference<ISymbol> symbol)>.GetInstance(
                    compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols);
 
                foreach (var reference in compilation.References)
                {
                    var symbol = compilation.GetAssemblyOrModuleSymbol(reference);
                    if (symbol == null)
                        continue;
 
                    secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference<ISymbol>(symbol)));
                }
 
                // Sort all the secondary symbols by their hash.  This will allow us to easily binary search for
                // them afterwards. Note: it is fine for multiple symbols to have the same reference hash.  The
                // search algorithm will account for that.
                secondarySymbols.Sort(WeakSymbolComparer.Instance);
                return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable());
            }
 
            public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary)
            {
                if (primary)
                {
                    return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) ||
                           symbol.Equals(this.PrimaryDynamicSymbol.GetTarget());
                }
                else
                {
                    var secondarySymbols = this.SecondaryReferencedSymbols;
 
                    var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol);
 
                    // The secondary symbol array is sorted by the symbols' hash codes.  So do a binary search to find
                    // the location we should start looking at.
                    var index = secondarySymbols.BinarySearch((symbolHash, null!), WeakSymbolComparer.Instance);
                    if (index < 0)
                        return false;
 
                    // Could have multiple symbols with the same hash.  They will all be placed next to each other,
                    // so walk backward to hit the first.
                    while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash)
                        index--;
 
                    // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match.
                    while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash)
                    {
                        var cached = secondarySymbols[index].symbol;
                        if (cached.TryGetTarget(out var otherSymbol) && otherSymbol == symbol)
                            return true;
 
                        index++;
                    }
 
                    return false;
                }
            }
 
            private class WeakSymbolComparer : IComparer<(int hashcode, WeakReference<ISymbol> symbol)>
            {
                public static readonly WeakSymbolComparer Instance = new WeakSymbolComparer();
 
                private WeakSymbolComparer()
                {
                }
 
                public int Compare((int hashcode, WeakReference<ISymbol> symbol) x, (int hashcode, WeakReference<ISymbol> symbol) y)
                    => x.hashcode - y.hashcode;
            }
        }
    }
}