/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Compilation/SyntaxAndDeclarationManager.cs
741 строка
34 KB
Todd Grunke
DeclarationTable allocation reductions (#81627)
17 дек 2025, 18:13
Не верифицирован
17 дек 2025, 18:13
383a15a
Код
Авторство
О чём код?
// 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 declTableBuilder = DeclarationTable.Empty.ToBuilder(); foreach (var tree in externalSyntaxTrees) { AppendAllSyntaxTrees( treesBuilder, tree, scriptClassName, resolver, messageProvider, isSubmission, ordinalMapBuilder, loadDirectiveMapBuilder, loadedSyntaxTreeMapBuilder, declMapBuilder, lastComputedMemberNamesMap, declTableBuilder); } return new State( treesBuilder.ToImmutableAndFree(), ordinalMapBuilder.ToImmutableDictionaryAndFree(), loadDirectiveMapBuilder.ToImmutableDictionaryAndFree(), loadedSyntaxTreeMapBuilder.ToImmutableDictionaryAndFree(), declMapBuilder.ToImmutableDictionaryAndFree(), lastComputedMemberNamesMap.ToImmutableDictionaryAndFree(), declTableBuilder.ToDeclarationTableAndFree()); } 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 declTableBuilder = state.DeclarationTable.ToBuilder(); 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, declTableBuilder); } state = new State( treesBuilder.ToImmutableAndFree(), ordinalMapBuilder.ToImmutableDictionary(), loadDirectiveMapBuilder.ToImmutableDictionary(), loadedSyntaxTreeMapBuilder.ToImmutableDictionary(), declMapBuilder.ToImmutableDictionary(), lastComputedMemberNamesMap.ToImmutableDictionary(), declTableBuilder.ToDeclarationTableAndFree()); 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, DeclarationTable.Builder declTableBuilder) { var sourceCodeKind = tree.Options.Kind; if (sourceCodeKind == SourceCodeKind.Script) { AppendAllLoadedSyntaxTrees(treesBuilder, tree, scriptClassName, resolver, messageProvider, isSubmission, ordinalMapBuilder, loadDirectiveMapBuilder, loadedSyntaxTreeMapBuilder, declMapBuilder, lastComputedMemberNamesMap, declTableBuilder); } // 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, declTableBuilder); 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, DeclarationTable.Builder declTableBuilder) { 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, declTableBuilder); } 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, DeclarationTable.Builder declTableBuilder) { var lazyRoot = new Lazy<RootSingleNamespaceDeclaration>(() => DeclarationTreeBuilder.ForTree(tree, scriptClassName, isSubmission, lastComputedMemberNames)); declMapBuilder.Add(tree, lazyRoot); // Callers are responsible for checking for existing entries. declTableBuilder.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 declTableBuilder = state.DeclarationTable.ToBuilder(); foreach (var tree in syntaxTrees) { if (removeSet.Contains(tree)) { loadDirectiveMap = loadDirectiveMap.Remove(tree); loadedSyntaxTreeMap = loadedSyntaxTreeMap.Remove(tree.FilePath); lastComputedMemberNamesMap.Remove(tree); RemoveSyntaxTreeFromDeclarationMapAndTable(tree, declMapBuilder, declTableBuilder); } 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(), declTableBuilder.ToDeclarationTableAndFree()); 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, DeclarationTable.Builder declTableBuilder) { var lazyRoot = declMap[tree]; declTableBuilder.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 declTableBuilder = state.DeclarationTable.ToBuilder(); 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, declTableBuilder); } 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, declTableBuilder); 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, declTableBuilder); 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(), declTableBuilder.ToDeclarationTableAndFree()); 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; } } }