File: Symbols\Metadata\PE\PENamespaceSymbol.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE
{
    /// <summary>
    /// The base class to represent a namespace imported from a PE/module. Namespaces that differ
    /// only by casing in name are not merged.
    /// </summary>
    internal abstract class PENamespaceSymbol
        : NamespaceSymbol
    {
        /// <summary>
        /// A map of namespaces immediately contained within this namespace 
        /// mapped by their name (case-sensitively).
        /// </summary>
        protected Dictionary<ReadOnlyMemory<char>, PENestedNamespaceSymbol> lazyNamespaces;
 
        /// <summary>
        /// A map of types immediately contained within this namespace 
        /// grouped by their name (case-sensitively).
        /// </summary>
        protected Dictionary<ReadOnlyMemory<char>, ImmutableArray<PENamedTypeSymbol>> lazyTypes;
 
        /// <summary>
        /// A map of NoPia local types immediately contained in this assembly.
        /// Maps type name (non-qualified) to the row id. Note, for VB we should use
        /// full name.
        /// </summary>
        private Dictionary<string, TypeDefinitionHandle> _lazyNoPiaLocalTypes;
 
        /// <summary>
        /// All type members in a flat array
        /// </summary>
        private ImmutableArray<PENamedTypeSymbol> _lazyFlattenedTypes;
 
        /// <summary>
        /// All namespace and type members in a flat array
        /// </summary>
        private ImmutableArray<Symbol> _lazyFlattenedNamespacesAndTypes;
 
        internal sealed override NamespaceExtent Extent
        {
            get
            {
                return new NamespaceExtent(this.ContainingPEModule);
            }
        }
 
        [PerformanceSensitive(
            "https://github.com/dotnet/roslyn/issues/23582",
            Constraint = "Provide " + nameof(ArrayBuilder<Symbol>) + " capacity to reduce number of allocations.",
            AllowGenericEnumeration = false)]
        public sealed override ImmutableArray<Symbol> GetMembers()
        {
            if (_lazyFlattenedNamespacesAndTypes.IsDefault)
            {
                EnsureAllMembersLoaded();
                ImmutableInterlocked.InterlockedExchange(ref _lazyFlattenedNamespacesAndTypes, calculateMembers());
            }
 
            return _lazyFlattenedNamespacesAndTypes;
 
            ImmutableArray<Symbol> calculateMembers()
            {
                var memberTypes = GetMemberTypesPrivate();
 
                if (lazyNamespaces.Count == 0)
                    return StaticCast<Symbol>.From(memberTypes);
 
                var builder = ArrayBuilder<Symbol>.GetInstance(memberTypes.Length + lazyNamespaces.Count);
 
                builder.AddRange(memberTypes);
                foreach (var pair in lazyNamespaces)
                {
                    builder.Add(pair.Value);
                }
 
                return builder.ToImmutableAndFree();
            }
        }
 
        private ImmutableArray<NamedTypeSymbol> GetMemberTypesPrivate()
        {
            //assume that EnsureAllMembersLoaded() has initialize lazyTypes
            if (_lazyFlattenedTypes.IsDefault)
            {
                var flattened = lazyTypes.Flatten();
                ImmutableInterlocked.InterlockedExchange(ref _lazyFlattenedTypes, flattened);
            }
 
            return StaticCast<NamedTypeSymbol>.From(_lazyFlattenedTypes);
        }
 
        internal override NamespaceSymbol GetNestedNamespace(ReadOnlyMemory<char> name)
        {
            EnsureAllMembersLoaded();
 
            if (lazyNamespaces.TryGetValue(name, out var ns))
            {
                return ns;
            }
 
            return null;
        }
 
        public sealed override ImmutableArray<Symbol> GetMembers(ReadOnlyMemory<char> name)
        {
            EnsureAllMembersLoaded();
 
            PENestedNamespaceSymbol ns = null;
            ImmutableArray<PENamedTypeSymbol> t;
 
            if (lazyNamespaces.TryGetValue(name, out ns))
            {
                if (lazyTypes.TryGetValue(name, out t))
                {
                    // TODO - Eliminate the copy by storing all members and type members instead of non-type and type members?
                    return StaticCast<Symbol>.From(t).Add(ns);
                }
                else
                {
                    return ImmutableArray.Create<Symbol>(ns);
                }
            }
            else if (lazyTypes.TryGetValue(name, out t))
            {
                return StaticCast<Symbol>.From(t);
            }
 
            return ImmutableArray<Symbol>.Empty;
        }
 
        public sealed override ImmutableArray<NamedTypeSymbol> GetTypeMembers()
        {
            EnsureAllMembersLoaded();
 
            return GetMemberTypesPrivate();
        }
 
        public sealed override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name)
        {
            EnsureAllMembersLoaded();
 
            ImmutableArray<PENamedTypeSymbol> t;
 
            return lazyTypes.TryGetValue(name, out t)
                ? StaticCast<NamedTypeSymbol>.From(t)
                : ImmutableArray<NamedTypeSymbol>.Empty;
        }
 
        public sealed override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name, int arity)
        {
            return GetTypeMembers(name).WhereAsArray((type, arity) => type.Arity == arity, arity);
        }
 
        public sealed override ImmutableArray<Location> Locations
        {
            get
            {
                return ContainingPEModule.MetadataLocation.Cast<MetadataLocation, Location>();
            }
        }
 
        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
        {
            get
            {
                return ImmutableArray<SyntaxReference>.Empty;
            }
        }
 
        /// <summary>
        /// Returns PEModuleSymbol containing the namespace.
        /// </summary>
        /// <returns>PEModuleSymbol containing the namespace.</returns>
        internal abstract PEModuleSymbol ContainingPEModule { get; }
 
        protected abstract void EnsureAllMembersLoaded();
 
        /// <summary>
        /// Initializes namespaces and types maps with information about 
        /// namespaces and types immediately contained within this namespace.
        /// </summary>
        /// <param name="typesByNS">
        /// The sequence of groups of TypeDef row ids for types contained within the namespace, 
        /// recursively including those from nested namespaces. The row ids must be grouped by the 
        /// fully-qualified namespace name case-sensitively. There could be multiple groups 
        /// for each fully-qualified namespace name. The groups must be sorted by
        /// their key in case-sensitive manner. Empty string must be used as namespace name for types 
        /// immediately contained within Global namespace. Therefore, all types in this namespace, if any, 
        /// must be in several first IGroupings.
        /// </param>
        protected void LoadAllMembers(IEnumerable<IGrouping<string, TypeDefinitionHandle>> typesByNS)
        {
            Debug.Assert(typesByNS != null);
 
            // A sequence of groups of TypeDef row ids for types immediately contained within this namespace.
            IEnumerable<IGrouping<string, TypeDefinitionHandle>> nestedTypes = null;
 
            // A sequence with information about namespaces immediately contained within this namespace.
            // For each pair:
            //    Key - contains simple name of a child namespace.
            //    Value - contains a sequence similar to the one passed to this function, but
            //            calculated for the child namespace. 
            IEnumerable<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>> nestedNamespaces = null;
            bool isGlobalNamespace = this.IsGlobalNamespace;
 
            MetadataHelpers.GetInfoForImmediateNamespaceMembers(
                isGlobalNamespace,
                isGlobalNamespace ? 0 : GetQualifiedNameLength(),
                typesByNS,
                StringComparer.Ordinal,
                out nestedTypes, out nestedNamespaces);
 
            LazyInitializeNamespaces(nestedNamespaces);
 
            LazyInitializeTypes(nestedTypes);
        }
 
        private int GetQualifiedNameLength()
        {
            int length = this.Name.Length;
 
            var parent = ContainingNamespace;
            while (parent?.IsGlobalNamespace == false)
            {
                // add name of the parent + "."
                length += parent.Name.Length + 1;
                parent = parent.ContainingNamespace;
            }
 
            return length;
        }
 
        /// <summary>
        /// Create symbols for nested namespaces and initialize namespaces map.
        /// </summary>
        private void LazyInitializeNamespaces(
            IEnumerable<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>> childNamespaces)
        {
            if (this.lazyNamespaces == null)
            {
                var namespaces = new Dictionary<ReadOnlyMemory<char>, PENestedNamespaceSymbol>(ReadOnlyMemoryOfCharComparer.Instance);
 
                foreach (var child in childNamespaces)
                {
                    var c = new PENestedNamespaceSymbol(child.Key, this, child.Value);
                    namespaces.Add(c.Name.AsMemory(), c);
                }
 
                Interlocked.CompareExchange(ref this.lazyNamespaces, namespaces, null);
            }
        }
 
        /// <summary>
        /// Create symbols for nested types and initialize types map.
        /// </summary>
        private void LazyInitializeTypes(IEnumerable<IGrouping<string, TypeDefinitionHandle>> typeGroups)
        {
            if (this.lazyTypes == null)
            {
                var moduleSymbol = ContainingPEModule;
 
                var children = ArrayBuilder<PENamedTypeSymbol>.GetInstance();
                var skipCheckForPiaType = !moduleSymbol.Module.ContainsNoPiaLocalTypes();
                Dictionary<string, TypeDefinitionHandle> noPiaLocalTypes = null;
 
                foreach (var g in typeGroups)
                {
                    foreach (var t in g)
                    {
                        if (skipCheckForPiaType || !moduleSymbol.Module.IsNoPiaLocalType(t))
                        {
                            children.Add(PENamedTypeSymbol.Create(moduleSymbol, this, t, g.Key));
                        }
                        else
                        {
                            try
                            {
                                string typeDefName = moduleSymbol.Module.GetTypeDefNameOrThrow(t);
 
                                if (noPiaLocalTypes == null)
                                {
                                    noPiaLocalTypes = new Dictionary<string, TypeDefinitionHandle>(StringOrdinalComparer.Instance);
                                }
 
                                noPiaLocalTypes[typeDefName] = t;
                            }
                            catch (BadImageFormatException)
                            { }
                        }
                    }
                }
 
                var typesDict = children.ToDictionary(c => c.Name.AsMemory(), ReadOnlyMemoryOfCharComparer.Instance);
                children.Free();
 
                if (noPiaLocalTypes != null)
                {
                    Interlocked.CompareExchange(ref _lazyNoPiaLocalTypes, noPiaLocalTypes, null);
                }
 
                var original = Interlocked.CompareExchange(ref this.lazyTypes, typesDict, null);
 
                // Build cache of TypeDef Tokens
                // Potentially this can be done in the background.
                if (original == null)
                {
                    moduleSymbol.OnNewTypeDeclarationsLoaded(typesDict);
                }
            }
        }
 
#nullable enable
 
        internal NamedTypeSymbol? UnifyIfNoPiaLocalType(ref MetadataTypeName emittedTypeName)
        {
            EnsureAllMembersLoaded();
            TypeDefinitionHandle typeDef;
 
            // See if this is a NoPia local type, which we should unify.
            // Note, VB should use FullName.
            if (_lazyNoPiaLocalTypes != null && _lazyNoPiaLocalTypes.TryGetValue(emittedTypeName.TypeName, out typeDef))
            {
                var result = (NamedTypeSymbol)new MetadataDecoder(ContainingPEModule).GetTypeOfToken(typeDef, out bool isNoPiaLocalType);
                Debug.Assert(isNoPiaLocalType);
                Debug.Assert(result is not null);
                return result;
            }
 
            return null;
        }
    }
}