File: Symbols\Source\SourceNamespaceSymbol.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.Threading;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal sealed partial class SourceNamespaceSymbol : NamespaceSymbol
    {
        private static readonly ImmutableDictionary<SingleNamespaceDeclaration, AliasesAndUsings> s_emptyMap =
            ImmutableDictionary<SingleNamespaceDeclaration, AliasesAndUsings>.Empty.WithComparers(ReferenceEqualityComparer.Instance);
 
        private readonly SourceModuleSymbol _module;
        private readonly Symbol _container;
        private readonly MergedNamespaceDeclaration _mergedDeclaration;
 
        private SymbolCompletionState _state;
        private ImmutableArray<Location> _locations;
        private Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamespaceOrTypeSymbol>> _nameToMembersMap;
        private Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamedTypeSymbol>> _nameToTypeMembersMap;
        private ImmutableArray<Symbol> _lazyAllMembers;
        private ImmutableArray<NamedTypeSymbol> _lazyTypeMembersUnordered;
 
        /// <summary>
        /// Should only be read using <see cref="GetAliasesAndUsings(SingleNamespaceDeclaration)"/>.
        /// </summary>
        private ImmutableDictionary<SingleNamespaceDeclaration, AliasesAndUsings> _aliasesAndUsings_doNotAccessDirectly = s_emptyMap;
#if DEBUG
        /// <summary>
        /// Should only be read using <see cref="GetAliasesAndUsingsForAsserts"/>.
        /// </summary>
        private ImmutableDictionary<SingleNamespaceDeclaration, AliasesAndUsings> _aliasesAndUsingsForAsserts_doNotAccessDirectly = s_emptyMap;
#endif
        private MergedGlobalAliasesAndUsings _lazyMergedGlobalAliasesAndUsings;
 
        private const int LazyAllMembersIsSorted = 0x1;   // Set if "lazyAllMembers" is sorted.
        private int _flags;
 
        private LexicalSortKey _lazyLexicalSortKey = LexicalSortKey.NotInitialized;
 
        internal SourceNamespaceSymbol(
            SourceModuleSymbol module, Symbol container,
            MergedNamespaceDeclaration mergedDeclaration,
            BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(mergedDeclaration != null);
            _module = module;
            _container = container;
            _mergedDeclaration = mergedDeclaration;
 
            foreach (var singleDeclaration in mergedDeclaration.Declarations)
                diagnostics.AddRange(singleDeclaration.Diagnostics);
        }
 
        internal MergedNamespaceDeclaration MergedDeclaration
            => _mergedDeclaration;
 
        public override Symbol ContainingSymbol
            => _container;
 
        public override AssemblySymbol ContainingAssembly
            => _module.ContainingAssembly;
 
        public override string Name
            => _mergedDeclaration.Name;
 
        internal override LexicalSortKey GetLexicalSortKey()
        {
            if (!_lazyLexicalSortKey.IsInitialized)
            {
                _lazyLexicalSortKey.SetFrom(_mergedDeclaration.GetLexicalSortKey(this.DeclaringCompilation));
            }
            return _lazyLexicalSortKey;
        }
 
        public override ImmutableArray<Location> Locations
        {
            get
            {
                if (_locations.IsDefault)
                {
                    ImmutableInterlocked.InterlockedCompareExchange(ref _locations,
                        _mergedDeclaration.NameLocations,
                        default);
                }
 
                return _locations;
            }
        }
 
#nullable enable
        public override Location? TryGetFirstLocation()
            => _mergedDeclaration.Declarations is [var declaration, ..] ? declaration.NameLocation : null;
#nullable disable
 
        public override bool HasLocationContainedWithin(SyntaxTree tree, TextSpan declarationSpan, out bool wasZeroWidthMatch)
        {
            // Avoid the allocation of .Locations in the base method.
            foreach (var decl in _mergedDeclaration.Declarations)
            {
                if (IsLocationContainedWithin(decl.NameLocation, tree, declarationSpan, out wasZeroWidthMatch))
                    return true;
            }
 
            wasZeroWidthMatch = false;
            return false;
        }
 
        private static readonly Func<SingleNamespaceDeclaration, SyntaxReference> s_declaringSyntaxReferencesSelector = d =>
            new NamespaceDeclarationSyntaxReference(d.SyntaxReference);
 
        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
            => ComputeDeclaringReferencesCore();
 
        private ImmutableArray<SyntaxReference> ComputeDeclaringReferencesCore()
        {
            // SyntaxReference in the namespace declaration points to the name node of the namespace decl node not
            // namespace decl node we want to return. here we will wrap the original syntax reference in
            // the translation syntax reference so that we can lazily manipulate a node return to the caller
            return _mergedDeclaration.Declarations.SelectAsArray(s_declaringSyntaxReferencesSelector);
        }
 
        internal override ImmutableArray<Symbol> GetMembersUnordered()
        {
            var result = _lazyAllMembers;
 
            if (result.IsDefault)
            {
                var members = StaticCast<Symbol>.From(this.GetNameToMembersMap().Flatten(null));  // don't sort.
                ImmutableInterlocked.InterlockedInitialize(ref _lazyAllMembers, members);
                result = _lazyAllMembers;
            }
 
            return result.ConditionallyDeOrder();
        }
 
        public override ImmutableArray<Symbol> GetMembers()
        {
            if ((_flags & LazyAllMembersIsSorted) != 0)
            {
                return _lazyAllMembers;
            }
            else
            {
                var allMembers = this.GetMembersUnordered();
 
                if (allMembers.Length >= 2)
                {
                    // The array isn't sorted. Sort it and remember that we sorted it.
                    allMembers = allMembers.Sort(LexicalOrderSymbolComparer.Instance);
                    ImmutableInterlocked.InterlockedExchange(ref _lazyAllMembers, allMembers);
                }
 
                ThreadSafeFlagOperations.Set(ref _flags, LazyAllMembersIsSorted);
                return allMembers;
            }
        }
 
        public override ImmutableArray<Symbol> GetMembers(ReadOnlyMemory<char> name)
        {
            ImmutableArray<NamespaceOrTypeSymbol> members;
            return this.GetNameToMembersMap().TryGetValue(name, out members)
                ? members.Cast<NamespaceOrTypeSymbol, Symbol>()
                : ImmutableArray<Symbol>.Empty;
        }
 
        internal override ImmutableArray<NamedTypeSymbol> GetTypeMembersUnordered()
        {
            if (_lazyTypeMembersUnordered.IsDefault)
            {
                var members = this.GetNameToTypeMembersMap().Flatten();
                ImmutableInterlocked.InterlockedInitialize(ref _lazyTypeMembersUnordered, members);
            }
 
            return _lazyTypeMembersUnordered;
        }
 
        public override ImmutableArray<NamedTypeSymbol> GetTypeMembers()
        {
            return this.GetNameToTypeMembersMap().Flatten(LexicalOrderSymbolComparer.Instance);
        }
 
        public override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name)
        {
            ImmutableArray<NamedTypeSymbol> members;
            return this.GetNameToTypeMembersMap().TryGetValue(name, out members)
                ? members
                : ImmutableArray<NamedTypeSymbol>.Empty;
        }
 
        public override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name, int arity)
        {
            return GetTypeMembers(name).WhereAsArray((s, arity) => s.Arity == arity, arity);
        }
 
        internal override ModuleSymbol ContainingModule
        {
            get
            {
                return _module;
            }
        }
 
        internal override NamespaceExtent Extent
        {
            get
            {
                return new NamespaceExtent(_module);
            }
        }
 
        private Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamespaceOrTypeSymbol>> GetNameToMembersMap()
        {
            if (_nameToMembersMap == null)
            {
                var diagnostics = BindingDiagnosticBag.GetInstance();
                if (Interlocked.CompareExchange(ref _nameToMembersMap, MakeNameToMembersMap(diagnostics), null) == null)
                {
                    // NOTE: the following is not cancellable.  Once we've set the
                    // members, we *must* do the following to make sure we're in a consistent state.
                    this.AddDeclarationDiagnostics(diagnostics);
                    RegisterDeclaredCorTypes();
 
                    // We may produce a SymbolDeclaredEvent for the enclosing namespace before events for its contained members
                    DeclaringCompilation.SymbolDeclaredEvent(this);
                    var wasSetThisThread = _state.NotePartComplete(CompletionPart.NameToMembersMap);
                    Debug.Assert(wasSetThisThread);
                }
 
                diagnostics.Free();
            }
 
            return _nameToMembersMap;
        }
 
        private Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamedTypeSymbol>> GetNameToTypeMembersMap()
        {
            if (_nameToTypeMembersMap == null)
            {
                // NOTE: This method depends on MakeNameToMembersMap() on creating a proper
                // NOTE: type of the array, see comments in MakeNameToMembersMap() for details
                Interlocked.CompareExchange(
                    ref _nameToTypeMembersMap,
                    ImmutableArrayExtensions.GetTypesFromMemberMap<ReadOnlyMemory<char>, NamespaceOrTypeSymbol, NamedTypeSymbol>(
                        GetNameToMembersMap(), ReadOnlyMemoryOfCharComparer.Instance),
                    comparand: null);
            }
 
            return _nameToTypeMembersMap;
        }
 
        private Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamespaceOrTypeSymbol>> MakeNameToMembersMap(BindingDiagnosticBag diagnostics)
        {
            // NOTE: Even though the resulting map stores ImmutableArray<NamespaceOrTypeSymbol> as
            // NOTE: values if the name is mapped into an array of named types, which is frequently
            // NOTE: the case, we actually create an array of NamedTypeSymbol[] and wrap it in
            // NOTE: ImmutableArray<NamespaceOrTypeSymbol>
            // NOTE:
            // NOTE: This way we can save time and memory in GetNameToTypeMembersMap() -- when we see that
            // NOTE: a name maps into values collection containing types only instead of allocating another
            // NOTE: array of NamedTypeSymbol[] we downcast the array to ImmutableArray<NamedTypeSymbol>
 
            var builder = s_nameToObjectPool.Allocate();
            foreach (var declaration in _mergedDeclaration.Children)
            {
                NamespaceOrTypeSymbol symbol = BuildSymbol(declaration, diagnostics);
                ImmutableArrayExtensions.AddToMultiValueDictionaryBuilder(builder, symbol.Name.AsMemory(), symbol);
            }
 
            var result = new Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamespaceOrTypeSymbol>>(builder.Count, ReadOnlyMemoryOfCharComparer.Instance);
            ImmutableArrayExtensions.CreateNameToMembersMap<ReadOnlyMemory<char>, NamespaceOrTypeSymbol, NamedTypeSymbol, NamespaceSymbol>(builder, result);
            builder.Free();
 
            CheckMembers(this, result, diagnostics);
 
            return result;
        }
 
        private static void CheckMembers(NamespaceSymbol @namespace, Dictionary<ReadOnlyMemory<char>, ImmutableArray<NamespaceOrTypeSymbol>> result, BindingDiagnosticBag diagnostics)
        {
            var memberOfArity = new Symbol[10];
            MergedNamespaceSymbol mergedAssemblyNamespace = null;
 
            if (@namespace.ContainingAssembly.Modules.Length > 1)
            {
                mergedAssemblyNamespace = @namespace.ContainingAssembly.GetAssemblyNamespace(@namespace) as MergedNamespaceSymbol;
            }
 
            foreach (var name in result.Keys)
            {
                Array.Clear(memberOfArity, 0, memberOfArity.Length);
                foreach (var symbol in result[name])
                {
                    var nts = symbol as SourceMemberContainerTypeSymbol;
                    // It should be impossible to have a type member of a source namespace symbol which is not a SourceMemberContainerTypeSymbol
                    Debug.Assert((object)nts != null || symbol is not TypeSymbol);
 
                    var arity = ((object)nts != null) ? nts.Arity : 0;
                    if (arity >= memberOfArity.Length)
                    {
                        Array.Resize(ref memberOfArity, arity + 1);
                    }
 
                    var other = memberOfArity[arity];
 
                    if ((object)other == null && (object)mergedAssemblyNamespace != null)
                    {
                        // Check for collision with declarations from added modules.
                        foreach (NamespaceSymbol constituent in mergedAssemblyNamespace.ConstituentNamespaces)
                        {
                            if ((object)constituent != (object)@namespace)
                            {
                                // For whatever reason native compiler only detects conflicts against types.
                                // It doesn't complain when source declares a type with the same name as
                                // a namespace in added module, but complains when source declares a namespace
                                // with the same name as a type in added module.
                                var types = constituent.GetTypeMembers(symbol.Name, arity);
 
                                if (types.Length > 0)
                                {
                                    other = types[0];
                                    // Since the error doesn't specify what added module this type belongs to, we can stop searching
                                    // at the first match.
                                    break;
                                }
                            }
                        }
                    }
 
                    if ((object)other != null)
                    {
                        // To decide whether type declarations are duplicates, we need to access members which are only meaningful on source original definition symbols.
                        Debug.Assert((object)nts?.OriginalDefinition == nts && (object)other.OriginalDefinition == other);
 
                        switch (nts, other)
                        {
                            case ({ } left, SourceMemberContainerTypeSymbol right) when isFileLocalTypeInSeparateFileFrom(right, left):
                            case ({ } left1, NamespaceOrTypeSymbol right1) when isFileLocalTypeInSeparateFileFrom(left1, right1):
                                // no error
                                break;
                            case ({ IsFileLocal: true }, _) or (_, SourceMemberContainerTypeSymbol { IsFileLocal: true }):
                                diagnostics.Add(ErrorCode.ERR_FileLocalDuplicateNameInNS, symbol.GetFirstLocationOrNone(), symbol.Name, @namespace);
                                break;
                            case ({ IsPartial: true }, SourceMemberContainerTypeSymbol { IsPartial: true }):
                                diagnostics.Add(ErrorCode.ERR_PartialTypeKindConflict, symbol.GetFirstLocationOrNone(), symbol);
                                break;
                            default:
                                diagnostics.Add(ErrorCode.ERR_DuplicateNameInNS, symbol.GetFirstLocationOrNone(), symbol.Name, @namespace);
                                break;
                        }
                    }
 
                    memberOfArity[arity] = symbol;
 
                    if ((object)nts != null)
                    {
                        //types declared at the namespace level may only have declared accessibility of public or internal (Section 3.5.1)
                        Accessibility declaredAccessibility = nts.DeclaredAccessibility;
                        if (declaredAccessibility != Accessibility.Public && declaredAccessibility != Accessibility.Internal)
                        {
                            diagnostics.Add(ErrorCode.ERR_NoNamespacePrivate, symbol.GetFirstLocationOrNone());
                        }
                    }
                }
            }
 
            static bool isFileLocalTypeInSeparateFileFrom(SourceMemberContainerTypeSymbol possibleFileLocalType, NamespaceOrTypeSymbol otherSymbol)
            {
                if (!possibleFileLocalType.IsFileLocal)
                {
                    return false;
                }
 
                var leftTree = possibleFileLocalType.MergedDeclaration.Declarations[0].Location.SourceTree;
                if (otherSymbol is SourceNamedTypeSymbol { MergedDeclaration.NameLocations: var typeNameLocations })
                {
                    return !typeNameLocations.Any(static (loc, leftTree) => (object)loc.SourceTree == leftTree, leftTree);
                }
                else if (otherSymbol is SourceNamespaceSymbol { MergedDeclaration.NameLocations: var namespaceNameLocations })
                {
                    return !namespaceNameLocations.Any(static (loc, leftTree) => (object)loc.SourceTree == leftTree, leftTree);
                }
 
                throw ExceptionUtilities.UnexpectedValue(otherSymbol);
            }
        }
 
        private NamespaceOrTypeSymbol BuildSymbol(MergedNamespaceOrTypeDeclaration declaration, BindingDiagnosticBag diagnostics)
        {
            switch (declaration.Kind)
            {
                case DeclarationKind.Namespace:
                    return new SourceNamespaceSymbol(_module, this, (MergedNamespaceDeclaration)declaration, diagnostics);
 
                case DeclarationKind.Struct:
                case DeclarationKind.Interface:
                case DeclarationKind.Enum:
                case DeclarationKind.Delegate:
                case DeclarationKind.Class:
                case DeclarationKind.Record:
                case DeclarationKind.RecordStruct:
                    return new SourceNamedTypeSymbol(this, (MergedTypeDeclaration)declaration, diagnostics);
 
                case DeclarationKind.Script:
                case DeclarationKind.Submission:
                case DeclarationKind.ImplicitClass:
                    return new ImplicitNamedTypeSymbol(this, (MergedTypeDeclaration)declaration, diagnostics);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(declaration.Kind);
            }
        }
 
        /// <summary>
        /// Register COR types declared in this namespace, if any, in the COR types cache.
        /// </summary>
        private void RegisterDeclaredCorTypes()
        {
            AssemblySymbol containingAssembly = ContainingAssembly;
 
            if (containingAssembly.KeepLookingForDeclaredSpecialTypes)
            {
                // Register newly declared COR types
                foreach (var array in _nameToMembersMap.Values)
                {
                    foreach (var member in array)
                    {
                        var type = member as NamedTypeSymbol;
 
                        if ((object)type != null && type.SpecialType != SpecialType.None)
                        {
                            containingAssembly.RegisterDeclaredSpecialType(type);
 
                            if (!containingAssembly.KeepLookingForDeclaredSpecialTypes)
                            {
                                return;
                            }
                        }
                    }
                }
            }
        }
 
        public override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (this.IsGlobalNamespace)
            {
                return true;
            }
 
            // Check if any namespace declaration block intersects with the given tree/span.
            foreach (var declaration in _mergedDeclaration.Declarations)
            {
                cancellationToken.ThrowIfCancellationRequested();
 
                var declarationSyntaxRef = declaration.SyntaxReference;
                if (declarationSyntaxRef.SyntaxTree != tree)
                {
                    continue;
                }
 
                if (!definedWithinSpan.HasValue)
                {
                    return true;
                }
 
                var syntax = NamespaceDeclarationSyntaxReference.GetSyntax(declarationSyntaxRef, cancellationToken);
                if (syntax.FullSpan.IntersectsWith(definedWithinSpan.Value))
                {
                    return true;
                }
            }
 
            return false;
        }
    }
}