File: Workspace\Solution\DocumentState.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal partial class DocumentState : TextDocumentState
    {
        private static readonly Func<string?, PreservationMode, string> s_fullParseLog = (path, mode) => $"{path} : {mode}";
 
        private static readonly ConditionalWeakTable<SyntaxTree, DocumentId> s_syntaxTreeToIdMap = new();
 
        // properties inherited from the containing project:
        public LanguageServices LanguageServices { get; }
        private readonly ParseOptions? _options;
 
        // null if the document doesn't support syntax trees:
        private readonly ValueSource<TreeAndVersion>? _treeSource;
 
        protected DocumentState(
            LanguageServices languageServices,
            IDocumentServiceProvider? documentServiceProvider,
            DocumentInfo.DocumentAttributes attributes,
            ParseOptions? options,
            ITextAndVersionSource textSource,
            LoadTextOptions loadTextOptions,
            ValueSource<TreeAndVersion>? treeSource)
            : base(languageServices.SolutionServices, documentServiceProvider, attributes, textSource, loadTextOptions)
        {
            Contract.ThrowIfFalse(_options is null == _treeSource is null);
 
            LanguageServices = languageServices;
            _options = options;
            _treeSource = treeSource;
        }
 
        public DocumentState(
            LanguageServices languageServices,
            DocumentInfo info,
            ParseOptions? options,
            LoadTextOptions loadTextOptions)
            : base(languageServices.SolutionServices, info, loadTextOptions)
        {
            LanguageServices = languageServices;
            _options = options;
 
            // If this is document that doesn't support syntax, then don't even bother holding
            // onto any tree source.  It will never be used to get a tree, and can only hurt us
            // by possibly holding onto data that might cause a slow memory leak.
            if (languageServices.GetService<ISyntaxTreeFactoryService>() == null)
            {
                _treeSource = null;
            }
            else
            {
                Contract.ThrowIfNull(options);
                _treeSource = CreateLazyFullyParsedTree(
                    TextAndVersionSource,
                    LoadTextOptions,
                    info.Attributes.SyntaxTreeFilePath,
                    options,
                    languageServices);
            }
        }
 
        public ValueSource<TreeAndVersion>? TreeSource => _treeSource;
 
        [MemberNotNullWhen(true, nameof(_treeSource))]
        [MemberNotNullWhen(true, nameof(TreeSource))]
        [MemberNotNullWhen(true, nameof(_options))]
        [MemberNotNullWhen(true, nameof(ParseOptions))]
        internal bool SupportsSyntaxTree
            => _treeSource != null;
 
        public ParseOptions? ParseOptions
            => _options;
 
        public SourceCodeKind SourceCodeKind
            => ParseOptions == null ? Attributes.SourceCodeKind : ParseOptions.Kind;
 
        public bool IsGenerated
            => Attributes.IsGenerated;
 
        protected static ValueSource<TreeAndVersion> CreateLazyFullyParsedTree(
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions,
            string? filePath,
            ParseOptions options,
            LanguageServices languageServices,
            PreservationMode mode = PreservationMode.PreserveValue)
        {
            return new AsyncLazy<TreeAndVersion>(
                c => FullyParseTreeAsync(newTextSource, loadTextOptions, filePath, options, languageServices, mode, c),
                c => FullyParseTree(newTextSource, loadTextOptions, filePath, options, languageServices, mode, c),
                cacheResult: true);
        }
 
        private static async Task<TreeAndVersion> FullyParseTreeAsync(
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions,
            string? filePath,
            ParseOptions options,
            LanguageServices languageServices,
            PreservationMode mode,
            CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.Workspace_Document_State_FullyParseSyntaxTree, s_fullParseLog, filePath, mode, cancellationToken))
            {
                var textAndVersion = await newTextSource.GetValueAsync(loadTextOptions, cancellationToken).ConfigureAwait(false);
                return CreateTreeAndVersion(filePath, options, languageServices, textAndVersion, cancellationToken);
            }
        }
 
        private static TreeAndVersion FullyParseTree(
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions,
            string? filePath,
            ParseOptions options,
            LanguageServices languageServices,
            PreservationMode mode,
            CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.Workspace_Document_State_FullyParseSyntaxTree, s_fullParseLog, filePath, mode, cancellationToken))
            {
                var textAndVersion = newTextSource.GetValue(loadTextOptions, cancellationToken);
                return CreateTreeAndVersion(filePath, options, languageServices, textAndVersion, cancellationToken);
            }
        }
 
        private static TreeAndVersion CreateTreeAndVersion(
            string? filePath,
            ParseOptions options,
            LanguageServices languageServices,
            TextAndVersion textAndVersion,
            CancellationToken cancellationToken)
        {
            var text = textAndVersion.Text;
 
            var treeFactory = languageServices.GetRequiredService<ISyntaxTreeFactoryService>();
 
            var tree = treeFactory.ParseSyntaxTree(filePath, options, text, cancellationToken);
 
            Contract.ThrowIfNull(tree);
            CheckTree(tree, text);
 
            // text version for this document should be unique. use it as a starting point.
            return new TreeAndVersion(tree, textAndVersion.Version);
        }
 
        private static ValueSource<TreeAndVersion> CreateLazyIncrementallyParsedTree(
            ValueSource<TreeAndVersion> oldTreeSource,
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions)
        {
            return new AsyncLazy<TreeAndVersion>(
                c => IncrementallyParseTreeAsync(oldTreeSource, newTextSource, loadTextOptions, c),
                c => IncrementallyParseTree(oldTreeSource, newTextSource, loadTextOptions, c),
                cacheResult: true);
        }
 
        private static async Task<TreeAndVersion> IncrementallyParseTreeAsync(
            ValueSource<TreeAndVersion> oldTreeSource,
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions,
            CancellationToken cancellationToken)
        {
            try
            {
                using (Logger.LogBlock(FunctionId.Workspace_Document_State_IncrementallyParseSyntaxTree, cancellationToken))
                {
                    var newTextAndVersion = await newTextSource.GetValueAsync(loadTextOptions, cancellationToken).ConfigureAwait(false);
                    var oldTreeAndVersion = await oldTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
                    return IncrementallyParse(newTextAndVersion, oldTreeAndVersion, cancellationToken);
                }
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private static TreeAndVersion IncrementallyParseTree(
            ValueSource<TreeAndVersion> oldTreeSource,
            ITextAndVersionSource newTextSource,
            LoadTextOptions loadTextOptions,
            CancellationToken cancellationToken)
        {
            try
            {
                using (Logger.LogBlock(FunctionId.Workspace_Document_State_IncrementallyParseSyntaxTree, cancellationToken))
                {
                    var newTextAndVersion = newTextSource.GetValue(loadTextOptions, cancellationToken);
                    var oldTreeAndVersion = oldTreeSource.GetValue(cancellationToken);
 
                    return IncrementallyParse(newTextAndVersion, oldTreeAndVersion, cancellationToken);
                }
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private static TreeAndVersion IncrementallyParse(
            TextAndVersion newTextAndVersion,
            TreeAndVersion oldTreeAndVersion,
            CancellationToken cancellationToken)
        {
            var newText = newTextAndVersion.Text;
            var oldTree = oldTreeAndVersion.Tree;
 
            var oldText = oldTree.GetText(cancellationToken);
            var newTree = oldTree.WithChangedText(newText);
            Contract.ThrowIfNull(newTree);
            CheckTree(newTree, newText, oldTree, oldText);
 
            return MakeNewTreeAndVersion(oldTree, oldText, oldTreeAndVersion.Version, newTree, newText, newTextAndVersion.Version);
        }
 
        private static TreeAndVersion MakeNewTreeAndVersion(SyntaxTree oldTree, SourceText oldText, VersionStamp oldVersion, SyntaxTree newTree, SourceText newText, VersionStamp newVersion)
        {
            var topLevelChanged = TopLevelChanged(oldTree, oldText, newTree, newText);
            var version = topLevelChanged ? newVersion : oldVersion;
            return new TreeAndVersion(newTree, version);
        }
 
        private const int MaxTextChangeRangeLength = 1024 * 4;
 
        private static bool TopLevelChanged(SyntaxTree oldTree, SourceText oldText, SyntaxTree newTree, SourceText newText)
        {
            // ** currently, it doesn't do any text based quick check. we can add them later if current logic is not performant enough for typing case.
            var change = newText.GetEncompassingTextChangeRange(oldText);
            if (change == default)
            {
                // nothing has changed
                return false;
            }
 
            // if texts are small enough, just use the equivalent to find out whether there was top level edits
            if (oldText.Length < MaxTextChangeRangeLength && newText.Length < MaxTextChangeRangeLength)
            {
                var topLevel = !newTree.IsEquivalentTo(oldTree, topLevel: true);
                return topLevel;
            }
 
            // okay, text is not small and whole text is changed, then we always treat it as top level edit
            if (change.NewLength == newText.Length)
            {
                return true;
            }
 
            // if changes are small enough, we use IsEquivalentTo to find out whether there was a top level edit
            if (change.Span.Length < MaxTextChangeRangeLength && change.NewLength < MaxTextChangeRangeLength)
            {
                var topLevel = !newTree.IsEquivalentTo(oldTree, topLevel: true);
                return topLevel;
            }
 
            // otherwise, we always consider top level change
            return true;
        }
 
        public bool HasContentChanged(DocumentState oldState)
        {
            return oldState._treeSource != _treeSource
                || HasTextChanged(oldState, ignoreUnchangeableDocument: false);
        }
 
        [Obsolete("Use TextDocumentState.HasTextChanged")]
        public bool HasTextChanged(DocumentState oldState)
            => HasTextChanged(oldState, ignoreUnchangeableDocument: false);
 
        public DocumentState UpdateChecksumAlgorithm(SourceHashAlgorithm checksumAlgorithm)
        {
            var newLoadTextOptions = new LoadTextOptions(checksumAlgorithm);
 
            if (LoadTextOptions == newLoadTextOptions)
            {
                return this;
            }
 
            // To keep the loaded SourceText consistent with the DocumentState,
            // avoid updating the options if the loader can't apply them on the loaded SourceText.
            if (!TextAndVersionSource.CanReloadText)
            {
                return this;
            }
 
            // TODO: we should be able to reuse the tree root
            var newTreeSource = SupportsSyntaxTree ? CreateLazyFullyParsedTree(
                TextAndVersionSource,
                newLoadTextOptions,
                Attributes.SyntaxTreeFilePath,
                _options,
                LanguageServices) : null;
 
            return new DocumentState(
                LanguageServices,
                Services,
                Attributes,
                _options,
                TextAndVersionSource,
                newLoadTextOptions,
                newTreeSource);
        }
 
        public DocumentState UpdateParseOptions(ParseOptions options, bool onlyPreprocessorDirectiveChange)
        {
            var originalSourceKind = this.SourceCodeKind;
 
            var newState = this.SetParseOptions(options, onlyPreprocessorDirectiveChange);
            if (newState.SourceCodeKind != originalSourceKind)
            {
                newState = newState.UpdateSourceCodeKind(originalSourceKind);
            }
 
            return newState;
        }
 
        private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocessorDirectiveChange)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
 
            if (!SupportsSyntaxTree)
            {
                throw new InvalidOperationException();
            }
 
            ValueSource<TreeAndVersion>? newTreeSource = null;
 
            // Optimization: if we are only changing preprocessor directives, and we've already parsed the existing tree
            // and it didn't have any, we can avoid a reparse since the tree will be parsed the same.
            //
            // We only need to care about `#if` directives as those are the only sorts of directives that can affect how
            // a tree is parsed.
            if (onlyPreprocessorDirectiveChange &&
                _treeSource.TryGetValue(out var existingTreeAndVersion))
            {
                var existingTree = existingTreeAndVersion.Tree;
 
                SyntaxTree? newTree = null;
 
                var syntaxKinds = LanguageServices.GetRequiredService<ISyntaxKindsService>();
                if (existingTree.TryGetRoot(out var existingRoot) && !existingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia))
                {
                    var treeFactory = LanguageServices.GetRequiredService<ISyntaxTreeFactoryService>();
                    newTree = treeFactory.CreateSyntaxTree(Attributes.SyntaxTreeFilePath, options, existingTree.Encoding, LoadTextOptions.ChecksumAlgorithm, existingRoot);
                }
 
                if (newTree is not null)
                    newTreeSource = ValueSource.Constant(new TreeAndVersion(newTree, existingTreeAndVersion.Version));
            }
 
            // If we weren't able to reuse in a smart way, just reparse
            newTreeSource ??= CreateLazyFullyParsedTree(
                TextAndVersionSource,
                LoadTextOptions,
                Attributes.SyntaxTreeFilePath,
                options,
                LanguageServices);
 
            return new DocumentState(
                LanguageServices,
                Services,
                Attributes.With(sourceCodeKind: options.Kind),
                options,
                TextAndVersionSource,
                LoadTextOptions,
                newTreeSource);
        }
 
        public DocumentState UpdateSourceCodeKind(SourceCodeKind kind)
        {
            if (this.ParseOptions == null || kind == this.SourceCodeKind)
            {
                return this;
            }
 
            return this.SetParseOptions(this.ParseOptions.WithKind(kind), onlyPreprocessorDirectiveChange: false);
        }
 
        // TODO: https://github.com/dotnet/roslyn/issues/37125
        // if FilePath is null, then this will change the name of the underlying tree, but we aren't producing a new tree in that case.
        public DocumentState UpdateName(string name)
            => UpdateAttributes(Attributes.With(name: name));
 
        public DocumentState UpdateFolders(IReadOnlyList<string> folders)
            => UpdateAttributes(Attributes.With(folders: folders));
 
        private DocumentState UpdateAttributes(DocumentInfo.DocumentAttributes attributes)
        {
            Debug.Assert(attributes != Attributes);
 
            return new DocumentState(
                LanguageServices,
                Services,
                attributes,
                _options,
                TextAndVersionSource,
                LoadTextOptions,
                _treeSource);
        }
 
        public DocumentState UpdateFilePath(string? filePath)
        {
            var newAttributes = Attributes.With(filePath: filePath);
            Debug.Assert(newAttributes != Attributes);
 
            // TODO: it's overkill to fully reparse the tree if we had the tree already; all we have to do is update the
            // file path and diagnostic options for that tree.
            var newTreeSource = SupportsSyntaxTree ?
                CreateLazyFullyParsedTree(
                    TextAndVersionSource,
                    LoadTextOptions,
                    newAttributes.SyntaxTreeFilePath,
                    _options,
                    LanguageServices) : null;
 
            return new DocumentState(
                LanguageServices,
                Services,
                newAttributes,
                _options,
                TextAndVersionSource,
                LoadTextOptions,
                newTreeSource);
        }
 
        public new DocumentState UpdateText(SourceText newText, PreservationMode mode)
            => (DocumentState)base.UpdateText(newText, mode);
 
        public new DocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
            => (DocumentState)base.UpdateText(newTextAndVersion, mode);
 
        public new DocumentState UpdateText(TextLoader loader, PreservationMode mode)
            => (DocumentState)base.UpdateText(loader, mode);
 
        protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental)
        {
            ValueSource<TreeAndVersion>? newTreeSource;
 
            if (!SupportsSyntaxTree)
            {
                newTreeSource = null;
            }
            else if (incremental)
            {
                newTreeSource = CreateLazyIncrementallyParsedTree(_treeSource, newTextSource, LoadTextOptions);
            }
            else
            {
                newTreeSource = CreateLazyFullyParsedTree(
                    newTextSource,
                    LoadTextOptions,
                    Attributes.SyntaxTreeFilePath,
                    _options,
                    LanguageServices,
                    mode); // TODO: understand why the mode is given here. If we're preserving text by identity, why also preserve the tree?
            }
 
            return new DocumentState(
                LanguageServices,
                Services,
                Attributes,
                _options,
                textSource: newTextSource,
                LoadTextOptions,
                treeSource: newTreeSource);
        }
 
        internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode)
        {
            if (!SupportsSyntaxTree)
            {
                throw new InvalidOperationException();
            }
 
            var newTextVersion = GetNewerVersion();
            var newTreeVersion = GetNewTreeVersionForUpdatedTree(newRoot, newTextVersion, mode);
 
            // determine encoding
            Encoding? encoding;
 
            if (TryGetSyntaxTree(out var priorTree))
            {
                // this is most likely available since UpdateTree is normally called after modifying the existing tree.
                encoding = priorTree.Encoding;
            }
            else if (TryGetText(out var priorText))
            {
                encoding = priorText.Encoding;
            }
            else
            {
                // the existing encoding was never observed so is unknown.
                encoding = null;
            }
 
            var syntaxTreeFactory = LanguageServices.GetRequiredService<ISyntaxTreeFactoryService>();
 
            Contract.ThrowIfNull(_options);
            var (text, treeAndVersion) = CreateTreeWithLazyText(newRoot, newTextVersion, newTreeVersion, encoding, LoadTextOptions.ChecksumAlgorithm, Attributes, _options, syntaxTreeFactory);
 
            return new DocumentState(
                LanguageServices,
                Services,
                Attributes,
                _options,
                textSource: text,
                LoadTextOptions,
                treeSource: ValueSource.Constant(treeAndVersion));
 
            // use static method so we don't capture references to this
            static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText(
                SyntaxNode newRoot,
                VersionStamp textVersion,
                VersionStamp treeVersion,
                Encoding? encoding,
                SourceHashAlgorithm checksumAlgorithm,
                DocumentInfo.DocumentAttributes attributes,
                ParseOptions options,
                ISyntaxTreeFactoryService factory)
            {
                var tree = factory.CreateSyntaxTree(attributes.SyntaxTreeFilePath, options, encoding, checksumAlgorithm, newRoot);
 
                // its okay to use a strong cached AsyncLazy here because the compiler layer SyntaxTree will also keep the text alive once its built.
                var lazyTextAndVersion = new TreeTextSource(
                    new AsyncLazy<SourceText>(
                        tree.GetTextAsync,
                        tree.GetText,
                        cacheResult: true),
                    textVersion);
 
                return (lazyTextAndVersion, new TreeAndVersion(tree, treeVersion));
            }
        }
 
        private VersionStamp GetNewTreeVersionForUpdatedTree(SyntaxNode newRoot, VersionStamp newTextVersion, PreservationMode mode)
        {
            RoslynDebug.Assert(_treeSource != null);
 
            if (mode != PreservationMode.PreserveIdentity)
            {
                return newTextVersion;
            }
 
            if (!_treeSource.TryGetValue(out var oldTreeAndVersion) || !oldTreeAndVersion!.Tree.TryGetRoot(out var oldRoot))
            {
                return newTextVersion;
            }
 
            return oldRoot.IsEquivalentTo(newRoot, topLevel: true) ? oldTreeAndVersion.Version : newTextVersion;
        }
 
        internal override Task<Diagnostic?> GetLoadDiagnosticAsync(CancellationToken cancellationToken)
        {
            if (TextAndVersionSource is TreeTextSource)
            {
                return SpecializedTasks.Null<Diagnostic>();
            }
 
            return base.GetLoadDiagnosticAsync(cancellationToken);
        }
 
        private VersionStamp GetNewerVersion()
        {
            if (TextAndVersionSource.TryGetValue(LoadTextOptions, out var textAndVersion))
            {
                return textAndVersion!.Version.GetNewerVersion();
            }
 
            if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
            {
                return treeAndVersion.Version.GetNewerVersion();
            }
 
            return VersionStamp.Create();
        }
 
        public bool TryGetSyntaxTree([NotNullWhen(returnValue: true)] out SyntaxTree? syntaxTree)
        {
            syntaxTree = null;
            if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
            {
                syntaxTree = treeAndVersion.Tree;
                BindSyntaxTreeToId(syntaxTree, Id);
                return true;
            }
 
            return false;
        }
 
        [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/23582", OftenCompletesSynchronously = true)]
        public async ValueTask<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationToken)
        {
            // operation should only be performed on documents that support syntax trees
            RoslynDebug.Assert(_treeSource != null);
 
            var treeAndVersion = await _treeSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
            // make sure there is an association between this tree and this doc id before handing it out
            BindSyntaxTreeToId(treeAndVersion.Tree, this.Id);
            return treeAndVersion.Tree;
        }
 
        internal SyntaxTree GetSyntaxTree(CancellationToken cancellationToken)
        {
            // operation should only be performed on documents that support syntax trees
            RoslynDebug.Assert(_treeSource != null);
 
            var treeAndVersion = _treeSource.GetValue(cancellationToken);
 
            // make sure there is an association between this tree and this doc id before handing it out
            BindSyntaxTreeToId(treeAndVersion.Tree, this.Id);
            return treeAndVersion.Tree;
        }
 
        public bool TryGetTopLevelChangeTextVersion(out VersionStamp version)
        {
            if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
            {
                version = treeAndVersion.Version;
                return true;
            }
            else
            {
                version = default;
                return false;
            }
        }
 
        public override async Task<VersionStamp> GetTopLevelChangeTextVersionAsync(CancellationToken cancellationToken)
        {
            if (_treeSource == null)
            {
                return await GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
            }
 
            if (_treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
            {
                return treeAndVersion.Version;
            }
 
            treeAndVersion = await _treeSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
            return treeAndVersion.Version;
        }
 
        private static void BindSyntaxTreeToId(SyntaxTree tree, DocumentId id)
        {
            if (!s_syntaxTreeToIdMap.TryGetValue(tree, out var existingId))
            {
                // Avoid closing over parameter 'id' on the method's fast path
                var localId = id;
                existingId = s_syntaxTreeToIdMap.GetValue(tree, t => localId);
            }
 
            Contract.ThrowIfFalse(existingId == id);
        }
 
        public static DocumentId? GetDocumentIdForTree(SyntaxTree tree)
        {
            s_syntaxTreeToIdMap.TryGetValue(tree, out var id);
            return id;
        }
 
        private static void CheckTree(
            SyntaxTree newTree,
            SourceText newText,
            SyntaxTree? oldTree = null,
            SourceText? oldText = null)
        {
            // this should be always true
            if (newTree.Length == newText.Length)
            {
                return;
            }
 
            var newTreeContent = newTree.GetRoot().ToFullString();
            var newTextContent = newText.ToString();
 
            var oldTreeContent = oldTree?.GetRoot().ToFullString();
            var oldTextContent = oldText?.ToString();
 
            // we time to time see (incremental) parsing bug where text <-> tree round tripping is broken.
            // send NFW for those cases, since we'll be in a very broken state at that point
            FatalError.ReportAndCatch(new Exception($"tree and text has different length {newTree.Length} vs {newText.Length}"), ErrorSeverity.Critical);
 
            // this will make sure that these variables are not thrown away in the dump
            GC.KeepAlive(newTreeContent);
            GC.KeepAlive(newTextContent);
            GC.KeepAlive(oldTreeContent);
            GC.KeepAlive(oldTextContent);
 
            GC.KeepAlive(newTree);
            GC.KeepAlive(newText);
            GC.KeepAlive(oldTree);
            GC.KeepAlive(oldText);
        }
    }
}