File: Compilation\SyntaxAndDeclarationManager.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.Diagnostics;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System.Runtime.CompilerServices;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class SyntaxAndDeclarationManager : CommonSyntaxAndDeclarationManager
    {
        private static readonly ObjectPool<Stack<SingleNamespaceOrTypeDeclaration>> s_declarationStack =
            new ObjectPool<Stack<SingleNamespaceOrTypeDeclaration>>(() => new Stack<SingleNamespaceOrTypeDeclaration>());
 
        private State _lazyState;
 
        internal SyntaxAndDeclarationManager(
            ImmutableArray<SyntaxTree> externalSyntaxTrees,
            string scriptClassName,
            SourceReferenceResolver resolver,
            CommonMessageProvider messageProvider,
            bool isSubmission,
            State state)
            : base(externalSyntaxTrees, scriptClassName, resolver, messageProvider, isSubmission)
        {
            _lazyState = state;
        }
 
        internal State GetLazyState()
        {
            if (_lazyState == null)
            {
                Interlocked.CompareExchange(ref _lazyState, CreateState(this.ExternalSyntaxTrees, this.ScriptClassName, this.Resolver, this.MessageProvider, this.IsSubmission), null);
            }
 
            return _lazyState;
        }
 
        private static State CreateState(
            ImmutableArray<SyntaxTree> externalSyntaxTrees,
            string scriptClassName,
            SourceReferenceResolver resolver,
            CommonMessageProvider messageProvider,
            bool isSubmission)
        {
            var treesBuilder = ArrayBuilder<SyntaxTree>.GetInstance();
            var ordinalMapBuilder = PooledDictionary<SyntaxTree, int>.GetInstance();
            var loadDirectiveMapBuilder = PooledDictionary<SyntaxTree, ImmutableArray<LoadDirective>>.GetInstance();
            var loadedSyntaxTreeMapBuilder = PooledDictionary<string, SyntaxTree>.GetInstance();
            var declMapBuilder = PooledDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>>.GetInstance();
            var lastComputedMemberNamesMap = PooledDictionary<SyntaxTree, OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>>.GetInstance();
            var declTable = DeclarationTable.Empty;
 
            foreach (var tree in externalSyntaxTrees)
            {
                AppendAllSyntaxTrees(
                    treesBuilder,
                    tree,
                    scriptClassName,
                    resolver,
                    messageProvider,
                    isSubmission,
                    ordinalMapBuilder,
                    loadDirectiveMapBuilder,
                    loadedSyntaxTreeMapBuilder,
                    declMapBuilder,
                    lastComputedMemberNamesMap,
                    ref declTable);
            }
 
            return new State(
                treesBuilder.ToImmutableAndFree(),
                ordinalMapBuilder.ToImmutableDictionaryAndFree(),
                loadDirectiveMapBuilder.ToImmutableDictionaryAndFree(),
                loadedSyntaxTreeMapBuilder.ToImmutableDictionaryAndFree(),
                declMapBuilder.ToImmutableDictionaryAndFree(),
                lastComputedMemberNamesMap.ToImmutableDictionaryAndFree(),
                declTable);
        }
 
        public SyntaxAndDeclarationManager AddSyntaxTrees(IEnumerable<SyntaxTree> trees)
        {
            var scriptClassName = this.ScriptClassName;
            var resolver = this.Resolver;
            var messageProvider = this.MessageProvider;
            var isSubmission = this.IsSubmission;
 
            var state = _lazyState;
            var newExternalSyntaxTrees = this.ExternalSyntaxTrees.AddRange(trees);
            if (state == null)
            {
                return this.WithExternalSyntaxTrees(newExternalSyntaxTrees);
            }
 
            var ordinalMapBuilder = state.OrdinalMap.ToBuilder();
            var loadDirectiveMapBuilder = state.LoadDirectiveMap.ToBuilder();
            var loadedSyntaxTreeMapBuilder = state.LoadedSyntaxTreeMap.ToBuilder();
            var declMapBuilder = state.RootNamespaces.ToBuilder();
            var lastComputedMemberNamesMap = state.LastComputedMemberNames.ToBuilder();
            var declTable = state.DeclarationTable;
 
            var treesBuilder = ArrayBuilder<SyntaxTree>.GetInstance();
            treesBuilder.AddRange(state.SyntaxTrees);
 
            foreach (var tree in trees)
            {
                AppendAllSyntaxTrees(
                        treesBuilder,
                        tree,
                        scriptClassName,
                        resolver,
                        messageProvider,
                        isSubmission,
                        ordinalMapBuilder,
                        loadDirectiveMapBuilder,
                        loadedSyntaxTreeMapBuilder,
                        declMapBuilder,
                        lastComputedMemberNamesMap,
                        ref declTable);
            }
 
            state = new State(
                treesBuilder.ToImmutableAndFree(),
                ordinalMapBuilder.ToImmutableDictionary(),
                loadDirectiveMapBuilder.ToImmutableDictionary(),
                loadedSyntaxTreeMapBuilder.ToImmutableDictionary(),
                declMapBuilder.ToImmutableDictionary(),
                lastComputedMemberNamesMap.ToImmutableDictionary(),
                declTable);
 
            return new SyntaxAndDeclarationManager(
                newExternalSyntaxTrees,
                scriptClassName,
                resolver,
                messageProvider,
                isSubmission,
                state);
        }
 
        /// <summary>
        /// Appends all trees (including any trees from #load'ed files).
        /// </summary>
        private static void AppendAllSyntaxTrees(
            ArrayBuilder<SyntaxTree> treesBuilder,
            SyntaxTree tree,
            string scriptClassName,
            SourceReferenceResolver resolver,
            CommonMessageProvider messageProvider,
            bool isSubmission,
            IDictionary<SyntaxTree, int> ordinalMapBuilder,
            IDictionary<SyntaxTree, ImmutableArray<LoadDirective>> loadDirectiveMapBuilder,
            IDictionary<string, SyntaxTree> loadedSyntaxTreeMapBuilder,
            IDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>> declMapBuilder,
            IDictionary<SyntaxTree, OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>> lastComputedMemberNamesMap,
            ref DeclarationTable declTable)
        {
            var sourceCodeKind = tree.Options.Kind;
            if (sourceCodeKind == SourceCodeKind.Script)
            {
                AppendAllLoadedSyntaxTrees(treesBuilder, tree, scriptClassName, resolver, messageProvider, isSubmission, ordinalMapBuilder, loadDirectiveMapBuilder, loadedSyntaxTreeMapBuilder, declMapBuilder, lastComputedMemberNamesMap, ref declTable);
            }
 
            // We're adding new trees, so passing in .Empty for lastComputedMemberNames as we do not have prior named
            // computed for them.  Note: there is no correctness concern here either.  lastComputedMemberNames is simply
            // used as a way to save on memory *if* items are present in it.  If not, we simply do the normal full work
            // to compute the new member names.
            AddSyntaxTreeToDeclarationMapAndTable(
                tree, scriptClassName, isSubmission, declMapBuilder,
                lastComputedMemberNames: OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>.Empty, ref declTable);
 
            treesBuilder.Add(tree);
            ordinalMapBuilder.Add(tree, ordinalMapBuilder.Count);
 
            // Fresh tree, so we have no computed names for it yet.
            lastComputedMemberNamesMap.Add(tree, OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>.Empty);
        }
 
        private static void AppendAllLoadedSyntaxTrees(
            ArrayBuilder<SyntaxTree> treesBuilder,
            SyntaxTree tree,
            string scriptClassName,
            SourceReferenceResolver resolver,
            CommonMessageProvider messageProvider,
            bool isSubmission,
            IDictionary<SyntaxTree, int> ordinalMapBuilder,
            IDictionary<SyntaxTree, ImmutableArray<LoadDirective>> loadDirectiveMapBuilder,
            IDictionary<string, SyntaxTree> loadedSyntaxTreeMapBuilder,
            IDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>> declMapBuilder,
            IDictionary<SyntaxTree, OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>> lastComputedMemberNamesMap,
            ref DeclarationTable declTable)
        {
            ArrayBuilder<LoadDirective> loadDirectives = null;
 
            foreach (var directive in tree.GetCompilationUnitRoot().GetLoadDirectives())
            {
                var fileToken = directive.File;
                var path = (string)fileToken.Value;
                if (path == null)
                {
                    // If there is no path, the parser should have some Diagnostics to report (if we're in an active region).
                    Debug.Assert(!directive.IsActive || tree.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error));
                    continue;
                }
 
                var diagnostics = DiagnosticBag.GetInstance();
                string resolvedFilePath = null;
                if (resolver == null)
                {
                    diagnostics.Add(
                        messageProvider.CreateDiagnostic(
                            (int)ErrorCode.ERR_SourceFileReferencesNotSupported,
                            directive.Location));
                }
                else
                {
                    resolvedFilePath = resolver.ResolveReference(path, baseFilePath: tree.FilePath);
                    if (resolvedFilePath == null)
                    {
                        diagnostics.Add(
                            messageProvider.CreateDiagnostic(
                                (int)ErrorCode.ERR_NoSourceFile,
                                fileToken.GetLocation(),
                                path,
                                CSharpResources.CouldNotFindFile));
                    }
                    else if (!loadedSyntaxTreeMapBuilder.ContainsKey(resolvedFilePath))
                    {
                        try
                        {
                            var code = resolver.ReadText(resolvedFilePath);
                            var loadedTree = SyntaxFactory.ParseSyntaxTree(
                                code,
                                tree.Options, // Use ParseOptions propagated from "external" tree.
                                resolvedFilePath);
 
                            // All #load'ed trees should have unique path information.
                            loadedSyntaxTreeMapBuilder.Add(loadedTree.FilePath, loadedTree);
 
                            AppendAllSyntaxTrees(
                                treesBuilder,
                                loadedTree,
                                scriptClassName,
                                resolver,
                                messageProvider,
                                isSubmission,
                                ordinalMapBuilder,
                                loadDirectiveMapBuilder,
                                loadedSyntaxTreeMapBuilder,
                                declMapBuilder,
                                lastComputedMemberNamesMap,
                                ref declTable);
                        }
                        catch (Exception e)
                        {
                            diagnostics.Add(
                                CommonCompiler.ToFileReadDiagnostics(messageProvider, e, resolvedFilePath),
                                fileToken.GetLocation());
                        }
                    }
                    else
                    {
                        // The path resolved, but we've seen this file before,
                        // so don't attempt to load it again.
                        Debug.Assert(diagnostics.IsEmptyWithoutResolution);
                    }
                }
 
                if (loadDirectives == null)
                {
                    loadDirectives = ArrayBuilder<LoadDirective>.GetInstance();
                }
                loadDirectives.Add(new LoadDirective(resolvedFilePath, diagnostics.ToReadOnlyAndFree()));
            }
 
            if (loadDirectives != null)
            {
                loadDirectiveMapBuilder.Add(tree, loadDirectives.ToImmutableAndFree());
            }
        }
 
        private static void AddSyntaxTreeToDeclarationMapAndTable(
            SyntaxTree tree,
            string scriptClassName,
            bool isSubmission,
            IDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>> declMapBuilder,
            OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>> lastComputedMemberNames,
            ref DeclarationTable declTable)
        {
            var lazyRoot = new Lazy<RootSingleNamespaceDeclaration>(() => DeclarationTreeBuilder.ForTree(tree, scriptClassName, isSubmission, lastComputedMemberNames));
            declMapBuilder.Add(tree, lazyRoot); // Callers are responsible for checking for existing entries.
            declTable = declTable.AddRootDeclaration(lazyRoot);
        }
 
        public SyntaxAndDeclarationManager RemoveSyntaxTrees(HashSet<SyntaxTree> trees)
        {
            var state = _lazyState;
            var newExternalSyntaxTrees = this.ExternalSyntaxTrees.RemoveAll(t => trees.Contains(t));
            if (state == null)
            {
                return this.WithExternalSyntaxTrees(newExternalSyntaxTrees);
            }
 
            var syntaxTrees = state.SyntaxTrees;
            var loadDirectiveMap = state.LoadDirectiveMap;
            var loadedSyntaxTreeMap = state.LoadedSyntaxTreeMap;
            var removeSet = PooledHashSet<SyntaxTree>.GetInstance();
            foreach (var tree in trees)
            {
                int unused1;
                ImmutableArray<LoadDirective> unused2;
                GetRemoveSet(
                    tree,
                    includeLoadedTrees: true,
                    syntaxTrees: syntaxTrees,
                    syntaxTreeOrdinalMap: state.OrdinalMap,
                    loadDirectiveMap: loadDirectiveMap,
                    loadedSyntaxTreeMap: loadedSyntaxTreeMap,
                    removeSet: removeSet,
                    totalReferencedTreeCount: out unused1,
                    oldLoadDirectives: out unused2);
            }
 
            var treesBuilder = ArrayBuilder<SyntaxTree>.GetInstance();
            var ordinalMapBuilder = PooledDictionary<SyntaxTree, int>.GetInstance();
            var declMapBuilder = state.RootNamespaces.ToBuilder();
            var lastComputedMemberNamesMap = state.LastComputedMemberNames.ToBuilder();
            var declTable = state.DeclarationTable;
            foreach (var tree in syntaxTrees)
            {
                if (removeSet.Contains(tree))
                {
                    loadDirectiveMap = loadDirectiveMap.Remove(tree);
                    loadedSyntaxTreeMap = loadedSyntaxTreeMap.Remove(tree.FilePath);
                    lastComputedMemberNamesMap.Remove(tree);
                    RemoveSyntaxTreeFromDeclarationMapAndTable(tree, declMapBuilder, ref declTable);
                }
                else if (!IsLoadedSyntaxTree(tree, loadedSyntaxTreeMap))
                {
                    UpdateSyntaxTreesAndOrdinalMapOnly(
                        treesBuilder,
                        tree,
                        ordinalMapBuilder,
                        loadDirectiveMap,
                        loadedSyntaxTreeMap);
                }
            }
            removeSet.Free();
 
            state = new State(
                treesBuilder.ToImmutableAndFree(),
                ordinalMapBuilder.ToImmutableDictionaryAndFree(),
                loadDirectiveMap,
                loadedSyntaxTreeMap,
                declMapBuilder.ToImmutableDictionary(),
                lastComputedMemberNamesMap.ToImmutableDictionary(),
                declTable);
 
            return new SyntaxAndDeclarationManager(
                newExternalSyntaxTrees,
                this.ScriptClassName,
                this.Resolver,
                this.MessageProvider,
                this.IsSubmission,
                state);
        }
 
        /// <summary>
        /// Collects all the trees #load'ed by <paramref name="oldTree"/> (as well as
        /// <paramref name="oldTree"/> itself) and populates <paramref name="removeSet"/>
        /// with all the trees that are safe to remove (not #load'ed by any other tree).
        /// </summary>
        private static void GetRemoveSet(
            SyntaxTree oldTree,
            bool includeLoadedTrees,
            ImmutableArray<SyntaxTree> syntaxTrees,
            ImmutableDictionary<SyntaxTree, int> syntaxTreeOrdinalMap,
            ImmutableDictionary<SyntaxTree, ImmutableArray<LoadDirective>> loadDirectiveMap,
            ImmutableDictionary<string, SyntaxTree> loadedSyntaxTreeMap,
            HashSet<SyntaxTree> removeSet,
            out int totalReferencedTreeCount,
            out ImmutableArray<LoadDirective> oldLoadDirectives)
        {
            if (includeLoadedTrees && loadDirectiveMap.TryGetValue(oldTree, out oldLoadDirectives))
            {
                Debug.Assert(!oldLoadDirectives.IsEmpty);
                GetRemoveSetForLoadedTrees(oldLoadDirectives, loadDirectiveMap, loadedSyntaxTreeMap, removeSet);
            }
            else
            {
                oldLoadDirectives = ImmutableArray<LoadDirective>.Empty;
            }
 
            removeSet.Add(oldTree);
            totalReferencedTreeCount = removeSet.Count;
 
            // Check subsequent trees to see if they #load any of the trees we're about
            // to remove.  We don't want to remove a tree until the last external tree
            // that depends on it is removed.
            if (removeSet.Count > 1)
            {
                var ordinal = syntaxTreeOrdinalMap[oldTree];
                for (int i = ordinal + 1; i < syntaxTrees.Length; i++)
                {
                    var tree = syntaxTrees[i];
                    ImmutableArray<LoadDirective> loadDirectives;
                    if (loadDirectiveMap.TryGetValue(tree, out loadDirectives))
                    {
                        Debug.Assert(!loadDirectives.IsEmpty);
                        foreach (var directive in loadDirectives)
                        {
                            SyntaxTree loadedTree;
                            if (TryGetLoadedSyntaxTree(loadedSyntaxTreeMap, directive, out loadedTree))
                            {
                                removeSet.Remove(loadedTree);
                            }
                        }
                    }
                }
            }
        }
 
        private static void GetRemoveSetForLoadedTrees(
            ImmutableArray<LoadDirective> loadDirectives,
            ImmutableDictionary<SyntaxTree, ImmutableArray<LoadDirective>> loadDirectiveMap,
            ImmutableDictionary<string, SyntaxTree> loadedSyntaxTreeMap,
            HashSet<SyntaxTree> removeSet)
        {
            foreach (var directive in loadDirectives)
            {
                if (directive.ResolvedPath != null)
                {
                    SyntaxTree loadedTree;
                    if (TryGetLoadedSyntaxTree(loadedSyntaxTreeMap, directive, out loadedTree) && removeSet.Add(loadedTree))
                    {
                        ImmutableArray<LoadDirective> nestedLoadDirectives;
                        if (loadDirectiveMap.TryGetValue(loadedTree, out nestedLoadDirectives))
                        {
                            Debug.Assert(!nestedLoadDirectives.IsEmpty);
                            GetRemoveSetForLoadedTrees(nestedLoadDirectives, loadDirectiveMap, loadedSyntaxTreeMap, removeSet);
                        }
                    }
                }
            }
        }
 
        private static void RemoveSyntaxTreeFromDeclarationMapAndTable(
            SyntaxTree tree,
            IDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>> declMap,
            ref DeclarationTable declTable)
        {
            var lazyRoot = declMap[tree];
            declTable = declTable.RemoveRootDeclaration(lazyRoot);
            declMap.Remove(tree);
        }
 
        public SyntaxAndDeclarationManager ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree)
        {
            var state = _lazyState;
            var newExternalSyntaxTrees = this.ExternalSyntaxTrees.Replace(oldTree, newTree);
            if (state == null)
            {
                return this.WithExternalSyntaxTrees(newExternalSyntaxTrees);
            }
 
            var newLoadDirectivesSyntax = newTree.GetCompilationUnitRoot().GetLoadDirectives();
            var loadDirectivesHaveChanged = !oldTree.GetCompilationUnitRoot().GetLoadDirectives().SequenceEqual(newLoadDirectivesSyntax);
            var syntaxTrees = state.SyntaxTrees;
            var ordinalMap = state.OrdinalMap;
            var loadDirectiveMap = state.LoadDirectiveMap;
            var loadedSyntaxTreeMap = state.LoadedSyntaxTreeMap;
            var removeSet = PooledHashSet<SyntaxTree>.GetInstance();
            int totalReferencedTreeCount;
            ImmutableArray<LoadDirective> oldLoadDirectives;
            GetRemoveSet(
                oldTree,
                loadDirectivesHaveChanged,
                syntaxTrees,
                ordinalMap,
                loadDirectiveMap,
                loadedSyntaxTreeMap,
                removeSet,
                out totalReferencedTreeCount,
                out oldLoadDirectives);
 
            var loadDirectiveMapBuilder = loadDirectiveMap.ToBuilder();
            var loadedSyntaxTreeMapBuilder = loadedSyntaxTreeMap.ToBuilder();
            var declMapBuilder = state.RootNamespaces.ToBuilder();
            var lastComputedMemberNamesMap = state.LastComputedMemberNames.ToBuilder();
            var declTable = state.DeclarationTable;
 
            OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>> lastComputedMemberNames = tryGetLastComputedMemberNames(
                oldTree, declMapBuilder, lastComputedMemberNamesMap);
 
            foreach (var tree in removeSet)
            {
                loadDirectiveMapBuilder.Remove(tree);
                loadedSyntaxTreeMapBuilder.Remove(tree.FilePath);
                lastComputedMemberNamesMap.Remove(tree);
                RemoveSyntaxTreeFromDeclarationMapAndTable(tree, declMapBuilder, ref declTable);
            }
            removeSet.Free();
 
            var oldOrdinal = ordinalMap[oldTree];
            ImmutableArray<SyntaxTree> newTrees;
            if (loadDirectivesHaveChanged)
            {
                // Should have been removed above...
                Debug.Assert(!loadDirectiveMapBuilder.ContainsKey(oldTree));
                Debug.Assert(!loadDirectiveMapBuilder.ContainsKey(newTree));
 
                // If we're inserting new #load'ed trees, we'll rebuild
                // the whole syntaxTree array and the ordinalMap.
                var treesBuilder = ArrayBuilder<SyntaxTree>.GetInstance();
                var ordinalMapBuilder = PooledDictionary<SyntaxTree, int>.GetInstance();
                for (var i = 0; i <= (oldOrdinal - totalReferencedTreeCount); i++)
                {
                    var tree = syntaxTrees[i];
                    treesBuilder.Add(tree);
                    ordinalMapBuilder.Add(tree, i);
                }
 
                AppendAllSyntaxTrees(
                    treesBuilder,
                    newTree,
                    this.ScriptClassName,
                    this.Resolver,
                    this.MessageProvider,
                    this.IsSubmission,
                    ordinalMapBuilder,
                    loadDirectiveMapBuilder,
                    loadedSyntaxTreeMapBuilder,
                    declMapBuilder,
                    lastComputedMemberNamesMap,
                    ref declTable);
 
                for (var i = oldOrdinal + 1; i < syntaxTrees.Length; i++)
                {
                    var tree = syntaxTrees[i];
                    if (!IsLoadedSyntaxTree(tree, loadedSyntaxTreeMap))
                    {
                        UpdateSyntaxTreesAndOrdinalMapOnly(
                            treesBuilder,
                            tree,
                            ordinalMapBuilder,
                            loadDirectiveMap,
                            loadedSyntaxTreeMap);
                    }
                }
 
                newTrees = treesBuilder.ToImmutableAndFree();
                ordinalMap = ordinalMapBuilder.ToImmutableDictionaryAndFree();
                Debug.Assert(newTrees.Length == ordinalMap.Count);
            }
            else
            {
                AddSyntaxTreeToDeclarationMapAndTable(newTree, this.ScriptClassName, this.IsSubmission, declMapBuilder, lastComputedMemberNames, ref declTable);
 
                if (newLoadDirectivesSyntax.Any())
                {
                    // If load directives have not changed and there are new directives,
                    // then there should have been (matching) old directives as well.
                    Debug.Assert(!oldLoadDirectives.IsDefault);
                    Debug.Assert(!oldLoadDirectives.IsEmpty);
                    Debug.Assert(oldLoadDirectives.Length == newLoadDirectivesSyntax.Count);
                    loadDirectiveMapBuilder[newTree] = oldLoadDirectives;
                }
 
                Debug.Assert(ordinalMap.ContainsKey(oldTree)); // Checked by RemoveSyntaxTreeFromDeclarationMapAndTable
 
                newTrees = syntaxTrees.SetItem(oldOrdinal, newTree);
 
                ordinalMap = ordinalMap.Remove(oldTree);
                ordinalMap = ordinalMap.SetItem(newTree, oldOrdinal);
 
                lastComputedMemberNamesMap.Add(newTree, lastComputedMemberNames);
            }
 
            state = new State(
                newTrees,
                ordinalMap,
                loadDirectiveMapBuilder.ToImmutable(),
                loadedSyntaxTreeMapBuilder.ToImmutable(),
                declMapBuilder.ToImmutable(),
                lastComputedMemberNamesMap.ToImmutable(),
                declTable);
 
            return new SyntaxAndDeclarationManager(
                newExternalSyntaxTrees,
                this.ScriptClassName,
                this.Resolver,
                this.MessageProvider,
                this.IsSubmission,
                state);
 
            static OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>> tryGetLastComputedMemberNames(
                SyntaxTree oldTree,
                ImmutableDictionary<SyntaxTree, Lazy<RootSingleNamespaceDeclaration>>.Builder declMapBuilder,
                ImmutableDictionary<SyntaxTree, OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>>.Builder lastComputedMemberNamesMap)
            {
                var previousRootNamespaceDeclaration = declMapBuilder[oldTree];
                if (previousRootNamespaceDeclaration.IsValueCreated)
                {
                    // we computed the last root.  It will have the most up to date member names, with the highest
                    // chance of being reusable after the last edit.  So just return those if present.
                    Stack<SingleNamespaceOrTypeDeclaration> stack = s_declarationStack.Allocate();
                    stack.Push(previousRootNamespaceDeclaration.Value);
 
                    var builder = ArrayBuilder<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>.GetInstance();
 
                    do
                    {
                        var current = stack.Pop();
 
                        // As we walk down, push children on in reverse order so we get a lexical ordering walk (the
                        // same order as what the decl-tree-builder will perform).  This allows the decl-tree-builder to
                        // keep track of what type it's on in lexical order and look back into this array to see if it
                        // can reuse the prior member names for the corresponding type.
 
                        for (int i = current.Children.Length - 1; i >= 0; i--)
                            stack.Push(current.Children[i]);
 
                        // Process any type we see before we process its nested types.
                        if (current is not SingleTypeDeclaration singleType)
                            continue;
 
                        // Skip any types that don't cache member names.  This also allows us to coordinate with the
                        // actual builder, which is keeping track of which caching-member-name type index it is on to
                        // look back into the last-computed-member-names list.
                        if (!DeclarationTreeBuilder.CachesComputedMemberNames(singleType))
                            continue;
 
                        builder.Add(new WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>(singleType.MemberNames));
                    }
                    while (stack.Count > 0);
 
                    s_declarationStack.Free(stack);
 
                    return builder.ToOneOrManyAndFree();
                }
 
                // The previous root wasn't computed yet.  So just return whatever info we have from before.  Note: this
                // may be from several edits prior.  As long as all the intervening edits have not touched member names,
                // this will still allow us to reuse those sets.
                if (lastComputedMemberNamesMap.TryGetValue(oldTree, out var lastComputedMemberNames))
                    return lastComputedMemberNames;
 
                // Didn't have the current root, and didn't have any prior cached items.  No reuse of computed names
                // possible here.
                return OneOrMany<WeakReference<StrongBox<ImmutableSegmentedHashSet<string>>>>.Empty;
            }
        }
 
        internal SyntaxAndDeclarationManager WithExternalSyntaxTrees(ImmutableArray<SyntaxTree> trees)
        {
            return new SyntaxAndDeclarationManager(trees, this.ScriptClassName, this.Resolver, this.MessageProvider, this.IsSubmission, state: null);
        }
 
        internal static bool IsLoadedSyntaxTree(SyntaxTree tree, ImmutableDictionary<string, SyntaxTree> loadedSyntaxTreeMap)
        {
            SyntaxTree loadedTree;
            return loadedSyntaxTreeMap.TryGetValue(tree.FilePath, out loadedTree) && (tree == loadedTree);
        }
 
        private static void UpdateSyntaxTreesAndOrdinalMapOnly(
            ArrayBuilder<SyntaxTree> treesBuilder,
            SyntaxTree tree,
            IDictionary<SyntaxTree, int> ordinalMapBuilder,
            ImmutableDictionary<SyntaxTree, ImmutableArray<LoadDirective>> loadDirectiveMap,
            ImmutableDictionary<string, SyntaxTree> loadedSyntaxTreeMap)
        {
            var sourceCodeKind = tree.Options.Kind;
            if (sourceCodeKind == SourceCodeKind.Script)
            {
                ImmutableArray<LoadDirective> loadDirectives;
                if (loadDirectiveMap.TryGetValue(tree, out loadDirectives))
                {
                    Debug.Assert(!loadDirectives.IsEmpty);
                    foreach (var directive in loadDirectives)
                    {
                        var resolvedPath = directive.ResolvedPath;
                        Debug.Assert((resolvedPath != null) || !directive.Diagnostics.IsEmpty);
                        if (resolvedPath == null)
                        {
                            continue;
                        }
 
                        SyntaxTree loadedTree;
                        if (TryGetLoadedSyntaxTree(loadedSyntaxTreeMap, directive, out loadedTree))
                        {
                            UpdateSyntaxTreesAndOrdinalMapOnly(
                                treesBuilder,
                                loadedTree,
                                ordinalMapBuilder,
                                loadDirectiveMap,
                                loadedSyntaxTreeMap);
                        }
                    }
                }
            }
 
            treesBuilder.Add(tree);
 
            ordinalMapBuilder.Add(tree, ordinalMapBuilder.Count);
        }
 
        internal bool MayHaveReferenceDirectives()
        {
            var state = _lazyState;
            if (state == null)
            {
                var externalSyntaxTrees = this.ExternalSyntaxTrees;
                return externalSyntaxTrees.Any(static t => t.HasReferenceOrLoadDirectives());
            }
 
            return state.DeclarationTable.ReferenceDirectives.Any();
        }
 
        private static bool TryGetLoadedSyntaxTree(ImmutableDictionary<string, SyntaxTree> loadedSyntaxTreeMap, LoadDirective directive, out SyntaxTree loadedTree)
        {
            if (loadedSyntaxTreeMap.TryGetValue(directive.ResolvedPath, out loadedTree))
            {
                return true;
            }
 
            // If we don't have a tree for this directive, there should be errors.
            Debug.Assert(directive.Diagnostics.HasAnyErrors());
 
            return false;
        }
    }
}