File: ReferenceManager\CommonReferenceManager.State.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    using MetadataOrDiagnostic = System.Object;
 
    internal abstract class CommonReferenceManager
    {
        /// <summary>
        /// Must be acquired whenever the following data are about to be modified:
        /// - Compilation.lazyAssemblySymbol
        /// - Compilation.referenceManager
        /// - ReferenceManager state
        /// - <see cref="AssemblyMetadata.CachedSymbols"/>
        /// - <see cref="Compilation.RetargetingAssemblySymbols"/>
        /// 
        /// All the above data should be updated at once while holding this lock.
        /// Once lazyAssemblySymbol is set the Compilation.referenceManager field and ReferenceManager
        /// state should not change.
        /// </summary>
        internal static object SymbolCacheAndReferenceManagerStateGuard = new object();
 
        /// <summary>
        /// Enumerates all referenced assemblies.
        /// </summary>
        internal abstract IEnumerable<KeyValuePair<MetadataReference, IAssemblySymbolInternal>> GetReferencedAssemblies();
 
        /// <summary>
        /// Enumerates all referenced assemblies and their aliases.
        /// </summary>
        internal abstract IEnumerable<(IAssemblySymbolInternal AssemblySymbol, ImmutableArray<string> Aliases)> GetReferencedAssemblyAliases();
 
        internal abstract MetadataReference? GetMetadataReference(IAssemblySymbolInternal? assemblySymbol);
        internal abstract ImmutableArray<MetadataReference> ExplicitReferences { get; }
        internal abstract ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> ImplicitReferenceResolutions { get; }
    }
 
    internal partial class CommonReferenceManager<TCompilation, TAssemblySymbol> : CommonReferenceManager
    {
        /// <summary>
        /// If the compilation being built represents an assembly its assembly name.
        /// If the compilation being built represents a module, the name of the 
        /// containing assembly or <see cref="Compilation.UnspecifiedModuleAssemblyName"/>
        /// if not specified (/moduleassemblyname command line option).
        /// </summary>
        internal readonly string SimpleAssemblyName;
 
        /// <summary>
        /// Used to compares assembly identities. 
        /// May implement unification and portability policies specific to the target platform.
        /// </summary>
        internal readonly AssemblyIdentityComparer IdentityComparer;
 
        /// <summary>
        /// Metadata observed by the compiler.
        /// May be shared across multiple Reference Managers.
        /// Access only under lock(<see cref="ObservedMetadata"/>).
        /// </summary>
        internal readonly Dictionary<MetadataReference, MetadataOrDiagnostic> ObservedMetadata;
 
        /// <summary>
        /// Once this is non-zero the state of the manager is fully initialized and immutable.
        /// </summary>
        private int _isBound;
 
        /// <summary>
        /// True if the compilation has a reference that refers back to the assembly being compiled.
        /// </summary>
        /// <remarks>
        /// If we have a circular reference the bound references can't be shared with other compilations.
        /// </remarks>
        private ThreeState _lazyHasCircularReference;
 
        /// <summary>
        /// A map from a metadata reference to an index to <see cref="_lazyReferencedAssemblies"/> array. Do not access
        /// directly, use <see cref="_lazyReferencedAssembliesMap"/> property instead.
        /// </summary>
        private Dictionary<MetadataReference, int>? _lazyReferencedAssembliesMap;
 
        /// <summary>
        /// A map from a net-module metadata reference to the index of the corresponding module
        /// symbol in the source assembly symbol for the current compilation.
        /// </summary>
        /// <remarks>
        /// Subtract one from the index (for the manifest module) to find the corresponding elements
        /// of <see cref="_lazyReferencedModules"/> and <see cref="_lazyReferencedModulesReferences"/>.
        /// </remarks>
        private Dictionary<MetadataReference, int>? _lazyReferencedModuleIndexMap;
 
        /// <summary>
        /// Maps (containing syntax tree file name, reference string) of #r directive to a resolved metadata reference.
        /// If multiple #r's in the same tree use the same value as a reference the resolved metadata reference is the same as well.
        /// </summary>
        private IDictionary<(string, string), MetadataReference>? _lazyReferenceDirectiveMap;
 
        /// <summary>
        /// Array of unique bound #r references.
        /// </summary>
        /// <remarks>
        /// The references are in the order they appear in syntax trees. This order is currently preserved 
        /// as syntax trees are added or removed, but we might decide to share reference manager between compilations
        /// with different order of #r's. It doesn't seem this would be an issue since all #r's within the compilation
        /// have the same "priority" with respect to each other.
        /// </remarks>
        private ImmutableArray<MetadataReference> _lazyDirectiveReferences;
 
        private ImmutableArray<MetadataReference> _lazyExplicitReferences;
 
        /// <summary>
        /// Stores the results of implicit reference resolutions.
        /// If <see cref="MetadataReferenceResolver.ResolveMissingAssemblies"/> is true the reference manager attempts to resolve assembly identities,
        /// that do not match any explicit metadata references passed to the compilation (or specified via #r directive).
        /// For each such assembly identity <see cref="MetadataReferenceResolver.ResolveMissingAssembly(MetadataReference, AssemblyIdentity)"/> is called
        /// and its result is captured in this map.
        /// The map also stores failures - the reference is null if the assembly of the given identity is not found by the resolver.
        /// This is important to maintain consistency, especially across multiple submissions (e.g. the reference is not found during compilation of the first submission
        /// but then it is available when the second submission is compiled).
        /// </summary>
        private ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?>? _lazyImplicitReferenceResolutions;
 
        /// <summary>
        /// Diagnostics produced during reference resolution and binding.
        /// </summary>
        /// <remarks>
        /// When reporting diagnostics be sure not to include any information that can't be shared among 
        /// compilations that share the same reference manager (such as full identity of the compilation, 
        /// simple assembly name is ok).
        /// </remarks>
        private ImmutableArray<Diagnostic> _lazyDiagnostics;
 
        /// <summary>
        /// COR library symbol, or null if the compilation itself is the COR library.
        /// </summary>
        /// <remarks>
        /// If the compilation being built is the COR library we don't want to store its source assembly symbol 
        /// here since we wouldn't be able to share the state among subsequent compilations that are derived from it
        /// (each of them has its own source assembly symbol).
        /// </remarks>
        private TAssemblySymbol? _lazyCorLibraryOpt;
 
        /// <summary>
        /// Standalone modules referenced by the compilation (doesn't include the manifest module of the compilation).
        /// </summary>
        /// <remarks>
        /// <see cref="_lazyReferencedModules"/>[i] corresponds to <see cref="_lazyReferencedModulesReferences"/>[i].
        /// </remarks>
        private ImmutableArray<PEModule> _lazyReferencedModules;
 
        /// <summary>
        /// References of standalone modules referenced by the compilation (doesn't include the manifest module of the compilation).
        /// </summary>
        /// <remarks>
        /// <see cref="_lazyReferencedModules"/>[i] corresponds to <see cref="_lazyReferencedModulesReferences"/>[i].
        /// </remarks>
        private ImmutableArray<ModuleReferences<TAssemblySymbol>> _lazyReferencedModulesReferences;
 
        /// <summary>
        /// Assemblies referenced directly by the source module of the compilation.
        /// </summary>
        private ImmutableArray<TAssemblySymbol> _lazyReferencedAssemblies;
 
        /// <summary>
        /// Aliases used by assemblies referenced directly by the source module of the compilation.
        /// </summary>
        /// <remarks>
        /// Aliases <see cref="_lazyAliasesOfReferencedAssemblies"/>[i] are of an assembly <see cref="_lazyReferencedAssemblies"/>[i].
        /// </remarks>
        private ImmutableArray<ImmutableArray<string>> _lazyAliasesOfReferencedAssemblies;
 
        /// <summary>
        /// A map capturing <see cref="MetadataReference"/>s that were "merged" to a single referenced assembly
        /// associated with a key in the map.
        /// The keys are a subset of keys from <see cref="_lazyReferencedAssembliesMap"/>.
        /// </summary>
        private ImmutableDictionary<MetadataReference, ImmutableArray<MetadataReference>>? _lazyMergedAssemblyReferencesMap;
 
        /// <summary>
        /// Unified assemblies referenced directly by the source module of the compilation.
        /// </summary>
        private ImmutableArray<UnifiedAssembly<TAssemblySymbol>> _lazyUnifiedAssemblies;
 
        public CommonReferenceManager(string simpleAssemblyName, AssemblyIdentityComparer identityComparer, Dictionary<MetadataReference, MetadataOrDiagnostic>? observedMetadata)
        {
            Debug.Assert(simpleAssemblyName != null);
            Debug.Assert(identityComparer != null);
 
            this.SimpleAssemblyName = simpleAssemblyName;
            this.IdentityComparer = identityComparer;
            this.ObservedMetadata = observedMetadata ?? new Dictionary<MetadataReference, MetadataOrDiagnostic>();
        }
 
        internal ImmutableArray<Diagnostic> Diagnostics
        {
            get
            {
                AssertBound();
                return _lazyDiagnostics;
            }
        }
 
        internal bool HasCircularReference
        {
            get
            {
                AssertBound();
                return _lazyHasCircularReference == ThreeState.True;
            }
        }
 
        internal Dictionary<MetadataReference, int> ReferencedAssembliesMap
        {
            get
            {
                AssertBound();
                return _lazyReferencedAssembliesMap;
            }
        }
 
        internal Dictionary<MetadataReference, int> ReferencedModuleIndexMap
        {
            get
            {
                AssertBound();
                return _lazyReferencedModuleIndexMap;
            }
        }
 
        internal IDictionary<(string, string), MetadataReference> ReferenceDirectiveMap
        {
            get
            {
                AssertBound();
                return _lazyReferenceDirectiveMap;
            }
        }
 
        internal ImmutableArray<MetadataReference> DirectiveReferences
        {
            get
            {
                AssertBound();
                return _lazyDirectiveReferences;
            }
        }
 
        internal override ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> ImplicitReferenceResolutions
        {
            get
            {
                AssertBound();
                return _lazyImplicitReferenceResolutions;
            }
        }
 
        internal override ImmutableArray<MetadataReference> ExplicitReferences
        {
            get
            {
                AssertBound();
                return _lazyExplicitReferences;
            }
        }
 
        #region Symbols necessary to set up source assembly and module
 
        internal TAssemblySymbol? CorLibraryOpt
        {
            get
            {
                AssertBound();
                return _lazyCorLibraryOpt;
            }
        }
 
        internal ImmutableArray<PEModule> ReferencedModules
        {
            get
            {
                AssertBound();
                return _lazyReferencedModules;
            }
        }
 
        internal ImmutableArray<ModuleReferences<TAssemblySymbol>> ReferencedModulesReferences
        {
            get
            {
                AssertBound();
                return _lazyReferencedModulesReferences;
            }
        }
 
        internal ImmutableArray<TAssemblySymbol> ReferencedAssemblies
        {
            get
            {
                AssertBound();
                return _lazyReferencedAssemblies;
            }
        }
 
        internal ImmutableArray<ImmutableArray<string>> AliasesOfReferencedAssemblies
        {
            get
            {
                AssertBound();
                return _lazyAliasesOfReferencedAssemblies;
            }
        }
 
        internal ImmutableDictionary<MetadataReference, ImmutableArray<MetadataReference>> MergedAssemblyReferencesMap
        {
            get
            {
                AssertBound();
                Debug.Assert(_lazyMergedAssemblyReferencesMap != null);
                return _lazyMergedAssemblyReferencesMap;
            }
        }
 
        internal ImmutableArray<UnifiedAssembly<TAssemblySymbol>> UnifiedAssemblies
        {
            get
            {
                AssertBound();
                return _lazyUnifiedAssemblies;
            }
        }
 
        #endregion
 
        /// <summary>
        /// Call only while holding <see cref="CommonReferenceManager.SymbolCacheAndReferenceManagerStateGuard"/>.
        /// </summary>
        [Conditional("DEBUG")]
        internal void AssertUnbound()
        {
            Debug.Assert(_isBound == 0);
            Debug.Assert(_lazyHasCircularReference == ThreeState.Unknown);
            Debug.Assert(_lazyReferencedAssembliesMap == null);
            Debug.Assert(_lazyReferencedModuleIndexMap == null);
            Debug.Assert(_lazyReferenceDirectiveMap == null);
            Debug.Assert(_lazyDirectiveReferences.IsDefault);
            Debug.Assert(_lazyImplicitReferenceResolutions == null);
            Debug.Assert(_lazyExplicitReferences.IsDefault);
            Debug.Assert(_lazyReferencedModules.IsDefault);
            Debug.Assert(_lazyReferencedModulesReferences.IsDefault);
            Debug.Assert(_lazyReferencedAssemblies.IsDefault);
            Debug.Assert(_lazyAliasesOfReferencedAssemblies.IsDefault);
            Debug.Assert(_lazyMergedAssemblyReferencesMap == null);
            Debug.Assert(_lazyUnifiedAssemblies.IsDefault);
            Debug.Assert(_lazyCorLibraryOpt == null);
        }
 
        [Conditional("DEBUG")]
        [MemberNotNull(nameof(_lazyReferencedAssembliesMap), nameof(_lazyReferencedModuleIndexMap), nameof(_lazyReferenceDirectiveMap), nameof(_lazyImplicitReferenceResolutions))]
        internal void AssertBound()
        {
            Debug.Assert(_isBound != 0);
            Debug.Assert(_lazyHasCircularReference != ThreeState.Unknown);
            Debug.Assert(_lazyReferencedAssembliesMap != null);
            Debug.Assert(_lazyReferencedModuleIndexMap != null);
            Debug.Assert(_lazyReferenceDirectiveMap != null);
            Debug.Assert(!_lazyDirectiveReferences.IsDefault);
            Debug.Assert(_lazyImplicitReferenceResolutions != null);
            Debug.Assert(!_lazyExplicitReferences.IsDefault);
            Debug.Assert(!_lazyReferencedModules.IsDefault);
            Debug.Assert(!_lazyReferencedModulesReferences.IsDefault);
            Debug.Assert(!_lazyReferencedAssemblies.IsDefault);
            Debug.Assert(!_lazyAliasesOfReferencedAssemblies.IsDefault);
            Debug.Assert(_lazyMergedAssemblyReferencesMap != null);
            Debug.Assert(!_lazyUnifiedAssemblies.IsDefault);
 
            // lazyCorLibrary is null if the compilation is corlib
            Debug.Assert(_lazyReferencedAssemblies.Length == 0 || _lazyCorLibraryOpt != null);
        }
 
        [Conditional("DEBUG")]
        internal void AssertCanReuseForCompilation(TCompilation compilation)
        {
            Debug.Assert(compilation.MakeSourceAssemblySimpleName() == this.SimpleAssemblyName);
        }
 
        internal bool IsBound
        {
            get
            {
                return _isBound != 0;
            }
        }
 
        /// <summary>
        /// Call only while holding <see cref="CommonReferenceManager.SymbolCacheAndReferenceManagerStateGuard"/>.
        /// </summary>
        internal void InitializeNoLock(
            Dictionary<MetadataReference, int> referencedAssembliesMap,
            Dictionary<MetadataReference, int> referencedModulesMap,
            IDictionary<(string, string), MetadataReference> boundReferenceDirectiveMap,
            ImmutableArray<MetadataReference> directiveReferences,
            ImmutableArray<MetadataReference> explicitReferences,
            ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> implicitReferenceResolutions,
            bool containsCircularReferences,
            ImmutableArray<Diagnostic> diagnostics,
            TAssemblySymbol? corLibraryOpt,
            ImmutableArray<PEModule> referencedModules,
            ImmutableArray<ModuleReferences<TAssemblySymbol>> referencedModulesReferences,
            ImmutableArray<TAssemblySymbol> referencedAssemblies,
            ImmutableArray<ImmutableArray<string>> aliasesOfReferencedAssemblies,
            ImmutableArray<UnifiedAssembly<TAssemblySymbol>> unifiedAssemblies,
            Dictionary<MetadataReference, ImmutableArray<MetadataReference>>? mergedAssemblyReferencesMapOpt)
        {
            AssertUnbound();
 
            Debug.Assert(referencedModules.Length == referencedModulesReferences.Length);
            Debug.Assert(referencedModules.Length == referencedModulesMap.Count);
            Debug.Assert(referencedAssemblies.Length == aliasesOfReferencedAssemblies.Length);
 
            _lazyReferencedAssembliesMap = referencedAssembliesMap;
            _lazyReferencedModuleIndexMap = referencedModulesMap;
            _lazyDiagnostics = diagnostics;
            _lazyReferenceDirectiveMap = boundReferenceDirectiveMap;
            _lazyDirectiveReferences = directiveReferences;
            _lazyExplicitReferences = explicitReferences;
            _lazyImplicitReferenceResolutions = implicitReferenceResolutions;
 
            _lazyCorLibraryOpt = corLibraryOpt;
            _lazyReferencedModules = referencedModules;
            _lazyReferencedModulesReferences = referencedModulesReferences;
            _lazyReferencedAssemblies = referencedAssemblies;
            _lazyAliasesOfReferencedAssemblies = aliasesOfReferencedAssemblies;
            _lazyMergedAssemblyReferencesMap = mergedAssemblyReferencesMapOpt?.ToImmutableDictionary() ?? ImmutableDictionary<MetadataReference, ImmutableArray<MetadataReference>>.Empty;
            _lazyUnifiedAssemblies = unifiedAssemblies;
            _lazyHasCircularReference = containsCircularReferences.ToThreeState();
 
            // once we flip this bit the state of the manager is immutable and available to any readers:
            Interlocked.Exchange(ref _isBound, 1);
        }
 
        /// <summary>
        /// Global namespaces of assembly references that have been superseded by an assembly reference with a higher version are 
        /// hidden behind <see cref="s_supersededAlias"/> to avoid ambiguity when they are accessed from source.
        /// All existing aliases of a superseded assembly are discarded.
        /// </summary>
        private static readonly ImmutableArray<string> s_supersededAlias = ImmutableArray.Create("<superseded>");
 
        protected static void BuildReferencedAssembliesAndModulesMaps(
            BoundInputAssembly[] bindingResult,
            ImmutableArray<MetadataReference> references,
            ImmutableArray<ResolvedReference> referenceMap,
            int referencedModuleCount,
            int explicitlyReferencedAssemblyCount,
            IReadOnlyDictionary<string, List<ReferencedAssemblyIdentity>> assemblyReferencesBySimpleName,
            bool supersedeLowerVersions,
            out Dictionary<MetadataReference, int> referencedAssembliesMap,
            out Dictionary<MetadataReference, int> referencedModulesMap,
            out ImmutableArray<ImmutableArray<string>> aliasesOfReferencedAssemblies,
            out Dictionary<MetadataReference, ImmutableArray<MetadataReference>>? mergedAssemblyReferencesMapOpt)
        {
            referencedAssembliesMap = new Dictionary<MetadataReference, int>(referenceMap.Length);
            referencedModulesMap = new Dictionary<MetadataReference, int>(referencedModuleCount);
            var aliasesOfReferencedAssembliesBuilder = ArrayBuilder<ImmutableArray<string>>.GetInstance(referenceMap.Length - referencedModuleCount);
            bool hasRecursiveAliases = false;
 
            mergedAssemblyReferencesMapOpt = null;
 
            for (int i = 0; i < referenceMap.Length; i++)
            {
                if (referenceMap[i].IsSkipped)
                {
                    continue;
                }
 
                if (referenceMap[i].Kind == MetadataImageKind.Module)
                {
                    // add 1 for the manifest module:
                    int moduleIndex = 1 + referenceMap[i].Index;
                    referencedModulesMap.Add(references[i], moduleIndex);
                }
                else
                {
                    // index into assembly data array
                    int assemblyIndex = referenceMap[i].Index;
                    Debug.Assert(aliasesOfReferencedAssembliesBuilder.Count == assemblyIndex);
 
                    MetadataReference reference = references[i];
                    referencedAssembliesMap.Add(reference, assemblyIndex);
                    aliasesOfReferencedAssembliesBuilder.Add(referenceMap[i].AliasesOpt);
 
                    if (!referenceMap[i].MergedReferences.IsEmpty)
                    {
                        (mergedAssemblyReferencesMapOpt ??= new Dictionary<MetadataReference, ImmutableArray<MetadataReference>>()).Add(reference, referenceMap[i].MergedReferences);
                    }
 
                    hasRecursiveAliases |= !referenceMap[i].RecursiveAliasesOpt.IsDefault;
                }
            }
 
            if (hasRecursiveAliases)
            {
                PropagateRecursiveAliases(bindingResult, referenceMap, aliasesOfReferencedAssembliesBuilder);
            }
 
            Debug.Assert(!aliasesOfReferencedAssembliesBuilder.Any(a => a.IsDefault));
 
            if (supersedeLowerVersions)
            {
                foreach (var assemblyReference in assemblyReferencesBySimpleName)
                {
                    // the item in the list is the highest version, by construction
                    for (int i = 1; i < assemblyReference.Value.Count; i++)
                    {
                        int assemblyIndex = assemblyReference.Value[i].GetAssemblyIndex(explicitlyReferencedAssemblyCount);
                        aliasesOfReferencedAssembliesBuilder[assemblyIndex] = s_supersededAlias;
                    }
                }
            }
 
            aliasesOfReferencedAssemblies = aliasesOfReferencedAssembliesBuilder.ToImmutableAndFree();
        }
 
        /// <summary>
        /// Calculates map from the identities of specified symbols to the corresponding identities in the original EnC baseline metadata.
        /// The map only includes an entry for identities that differ, i.e. for symbols representing assembly references of the current compilation that have different identities 
        /// than the corresponding identity in baseline metadata AssemblyRef table. The key comparer of the map ignores build and revision parts of the version number, 
        /// since these might change if the original version included wildcard.
        /// </summary>
        /// <param name="symbols">Assembly symbols for references of the current compilation.</param>
        /// <param name="originalIdentities">Identities in the baseline. <paramref name="originalIdentities"/>[i] corresponds to <paramref name="symbols"/>[i].</param>
        internal static ImmutableDictionary<AssemblyIdentity, AssemblyIdentity> GetAssemblyReferenceIdentityBaselineMap(ImmutableArray<TAssemblySymbol> symbols, ImmutableArray<AssemblyIdentity> originalIdentities)
        {
            Debug.Assert(originalIdentities.Length == symbols.Length);
 
            ImmutableDictionary<AssemblyIdentity, AssemblyIdentity>.Builder? lazyBuilder = null;
            for (int i = 0; i < originalIdentities.Length; i++)
            {
                var symbolIdentity = symbols[i].Identity;
                var versionPattern = symbols[i].AssemblyVersionPattern;
                var originalIdentity = originalIdentities[i];
 
                if (versionPattern is object)
                {
                    Debug.Assert(versionPattern.Build == ushort.MaxValue || versionPattern.Revision == ushort.MaxValue);
 
                    lazyBuilder = lazyBuilder ?? ImmutableDictionary.CreateBuilder<AssemblyIdentity, AssemblyIdentity>();
 
                    var sourceIdentity = symbolIdentity.WithVersion(versionPattern);
 
                    if (lazyBuilder.ContainsKey(sourceIdentity))
                    {
                        // The compilation references multiple assemblies whose versions only differ in auto-generated build and/or revision numbers.
                        throw new NotSupportedException(CodeAnalysisResources.CompilationReferencesAssembliesWithDifferentAutoGeneratedVersion);
                    }
 
                    lazyBuilder.Add(sourceIdentity, originalIdentity);
                }
                else
                {
                    // by construction of the arguments:
                    Debug.Assert(originalIdentity == symbolIdentity);
                }
            }
 
            return lazyBuilder?.ToImmutable() ?? ImmutableDictionary<AssemblyIdentity, AssemblyIdentity>.Empty;
        }
 
        internal static bool CompareVersionPartsSpecifiedInSource(Version version, Version candidateVersion, TAssemblySymbol candidateSymbol)
        {
            // major and minor parts must match exactly
 
            if (version.Major != candidateVersion.Major || version.Minor != candidateVersion.Minor)
            {
                return false;
            }
 
            // build and revision parts can differ only if the corresponding source versions were auto-generated:
            var versionPattern = candidateSymbol.AssemblyVersionPattern;
            Debug.Assert(versionPattern is null || versionPattern.Build == ushort.MaxValue || versionPattern.Revision == ushort.MaxValue);
 
            if ((versionPattern is null || versionPattern.Build < ushort.MaxValue) && version.Build != candidateVersion.Build)
            {
                return false;
            }
 
            if (versionPattern is null && version.Revision != candidateVersion.Revision)
            {
                return false;
            }
 
            return true;
        }
 
        // #r references are recursive, their aliases should be merged into all their dependencies.
        //
        // For example, if a compilation has a reference to LibA with alias A and the user #r's LibB with alias B,
        // which references LibA, LibA should be available under both aliases A and B. B is usually "global",
        // which means LibA namespaces should become available to the compilation without any qualification when #r LibB 
        // is encountered.
        // 
        // Pairs: (assembly index -- index into bindingResult array; index of the #r reference in referenceMap array).
        private static void PropagateRecursiveAliases(
            BoundInputAssembly[] bindingResult,
            ImmutableArray<ResolvedReference> referenceMap,
            ArrayBuilder<ImmutableArray<string>> aliasesOfReferencedAssembliesBuilder)
        {
            var assemblyIndicesToProcess = ArrayBuilder<int>.GetInstance();
            var visitedAssemblies = BitVector.Create(bindingResult.Length);
 
            // +1 for assembly being built
            Debug.Assert(bindingResult.Length == aliasesOfReferencedAssembliesBuilder.Count + 1);
 
            foreach (ResolvedReference reference in referenceMap)
            {
                if (!reference.IsSkipped && !reference.RecursiveAliasesOpt.IsDefault)
                {
                    var recursiveAliases = reference.RecursiveAliasesOpt;
 
                    Debug.Assert(reference.Kind == MetadataImageKind.Assembly);
                    visitedAssemblies.Clear();
 
                    Debug.Assert(assemblyIndicesToProcess.Count == 0);
                    assemblyIndicesToProcess.Add(reference.Index);
 
                    while (assemblyIndicesToProcess.Count > 0)
                    {
                        int assemblyIndex = assemblyIndicesToProcess.Pop();
                        visitedAssemblies[assemblyIndex] = true;
 
                        // merge aliases:
                        aliasesOfReferencedAssembliesBuilder[assemblyIndex] = MergedAliases.Merge(aliasesOfReferencedAssembliesBuilder[assemblyIndex], recursiveAliases);
 
                        // push dependencies onto the stack:
                        // +1 for the assembly being built:
                        var referenceBinding = bindingResult[assemblyIndex + 1].ReferenceBinding;
                        Debug.Assert(referenceBinding is object);
                        foreach (var binding in referenceBinding)
                        {
                            if (binding.IsBound)
                            {
                                // -1 for the assembly being built:
                                int dependentAssemblyIndex = binding.DefinitionIndex - 1;
                                if (!visitedAssemblies[dependentAssemblyIndex])
                                {
                                    assemblyIndicesToProcess.Add(dependentAssemblyIndex);
                                }
                            }
                        }
                    }
                }
            }
 
            for (int i = 0; i < aliasesOfReferencedAssembliesBuilder.Count; i++)
            {
                if (aliasesOfReferencedAssembliesBuilder[i].IsDefault)
                {
                    aliasesOfReferencedAssembliesBuilder[i] = ImmutableArray<string>.Empty;
                }
            }
 
            assemblyIndicesToProcess.Free();
        }
 
        #region Compilation APIs Implementation
 
        // for testing purposes
        internal IEnumerable<string> ExternAliases => AliasesOfReferencedAssemblies.SelectMany(aliases => aliases);
 
        internal sealed override IEnumerable<KeyValuePair<MetadataReference, IAssemblySymbolInternal>> GetReferencedAssemblies()
        {
            return ReferencedAssembliesMap.Select(ra => KeyValuePairUtil.Create(ra.Key, (IAssemblySymbolInternal)ReferencedAssemblies[ra.Value]));
        }
 
        internal TAssemblySymbol? GetReferencedAssemblySymbol(MetadataReference reference)
        {
            int index;
            return ReferencedAssembliesMap.TryGetValue(reference, out index) ? ReferencedAssemblies[index] : null;
        }
 
        internal int GetReferencedModuleIndex(MetadataReference reference)
        {
            int index;
            return ReferencedModuleIndexMap.TryGetValue(reference, out index) ? index : -1;
        }
 
        /// <summary>
        /// Gets the <see cref="MetadataReference"/> that corresponds to the assembly symbol. 
        /// </summary>
        internal override MetadataReference? GetMetadataReference(IAssemblySymbolInternal? assemblySymbol)
        {
            foreach (var entry in ReferencedAssembliesMap)
            {
                if ((object)ReferencedAssemblies[entry.Value] == assemblySymbol)
                {
                    return entry.Key;
                }
            }
 
            return null;
        }
 
        internal override IEnumerable<(IAssemblySymbolInternal AssemblySymbol, ImmutableArray<string> Aliases)> GetReferencedAssemblyAliases()
        {
            for (int i = 0; i < ReferencedAssemblies.Length; i++)
            {
                yield return (ReferencedAssemblies[i], AliasesOfReferencedAssemblies[i]);
            }
        }
 
        public bool DeclarationsAccessibleWithoutAlias(int referencedAssemblyIndex)
        {
            var aliases = AliasesOfReferencedAssemblies[referencedAssemblyIndex];
            return aliases.Length == 0 || aliases.IndexOf(MetadataReferenceProperties.GlobalAlias, StringComparer.Ordinal) >= 0;
        }
 
        #endregion
    }
}