File: Workspace\Solution\StateChecksums.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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Serialization
{
    internal sealed class SolutionStateChecksums : ChecksumWithChildren
    {
        public SolutionStateChecksums(
            Checksum attributesChecksum,
            ChecksumCollection projectChecksums,
            ChecksumCollection analyzerReferenceChecksums,
            Checksum frozenSourceGeneratedDocumentIdentity,
            Checksum frozenSourceGeneratedDocumentText)
            : this(ImmutableArray.Create<object>(attributesChecksum, projectChecksums, analyzerReferenceChecksums, frozenSourceGeneratedDocumentIdentity, frozenSourceGeneratedDocumentText))
        {
        }
 
        public SolutionStateChecksums(ImmutableArray<object> children) : base(children)
        {
        }
 
        public Checksum Attributes => (Checksum)Children[0];
        public ChecksumCollection Projects => (ChecksumCollection)Children[1];
        public ChecksumCollection AnalyzerReferences => (ChecksumCollection)Children[2];
        public Checksum FrozenSourceGeneratedDocumentIdentity => (Checksum)Children[3];
        public Checksum FrozenSourceGeneratedDocumentText => (Checksum)Children[4];
 
        public async Task FindAsync(
            SolutionState state,
            HashSet<Checksum> searchingChecksumsLeft,
            Dictionary<Checksum, object> result,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (searchingChecksumsLeft.Count == 0)
                return;
 
            // verify input
            if (searchingChecksumsLeft.Remove(Checksum))
                result[Checksum] = this;
 
            if (searchingChecksumsLeft.Remove(Attributes))
                result[Attributes] = state.SolutionAttributes;
 
            if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentIdentity))
            {
                Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentIdentity checksum if we didn't have a text in the first place.");
                result[FrozenSourceGeneratedDocumentIdentity] = state.FrozenSourceGeneratedDocumentState.Identity;
            }
 
            if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentText))
            {
                Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentState checksum if we didn't have a text in the first place.");
                result[FrozenSourceGeneratedDocumentText] = await SerializableSourceText.FromTextDocumentStateAsync(state.FrozenSourceGeneratedDocumentState, cancellationToken).ConfigureAwait(false);
            }
 
            if (searchingChecksumsLeft.Remove(Projects.Checksum))
            {
                result[Projects.Checksum] = Projects;
            }
 
            if (searchingChecksumsLeft.Remove(AnalyzerReferences.Checksum))
            {
                result[AnalyzerReferences.Checksum] = AnalyzerReferences;
            }
 
            foreach (var (_, projectState) in state.ProjectStates)
            {
                if (searchingChecksumsLeft.Count == 0)
                    break;
 
                // It's possible not all all our projects have checksums.  Specifically, we may have only been
                // asked to compute the checksum tree for a subset of projects that were all that a feature needed.
                if (projectState.TryGetStateChecksums(out var projectStateChecksums))
                    await projectStateChecksums.FindAsync(projectState, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false);
            }
 
            ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken);
        }
    }
 
    internal class ProjectStateChecksums : ChecksumWithChildren
    {
        public ProjectStateChecksums(
            Checksum infoChecksum,
            Checksum compilationOptionsChecksum,
            Checksum parseOptionsChecksum,
            ChecksumCollection documentChecksums,
            ChecksumCollection projectReferenceChecksums,
            ChecksumCollection metadataReferenceChecksums,
            ChecksumCollection analyzerReferenceChecksums,
            ChecksumCollection additionalDocumentChecksums,
            ChecksumCollection analyzerConfigDocumentChecksums)
            : this(ImmutableArray.Create<object>(
                infoChecksum,
                compilationOptionsChecksum,
                parseOptionsChecksum,
                documentChecksums,
                projectReferenceChecksums,
                metadataReferenceChecksums,
                analyzerReferenceChecksums,
                additionalDocumentChecksums,
                analyzerConfigDocumentChecksums))
        {
        }
 
        public ProjectStateChecksums(ImmutableArray<object> children) : base(children)
        {
        }
 
        public Checksum Info => (Checksum)Children[0];
        public Checksum CompilationOptions => (Checksum)Children[1];
        public Checksum ParseOptions => (Checksum)Children[2];
 
        public ChecksumCollection Documents => (ChecksumCollection)Children[3];
 
        public ChecksumCollection ProjectReferences => (ChecksumCollection)Children[4];
        public ChecksumCollection MetadataReferences => (ChecksumCollection)Children[5];
        public ChecksumCollection AnalyzerReferences => (ChecksumCollection)Children[6];
 
        public ChecksumCollection AdditionalDocuments => (ChecksumCollection)Children[7];
        public ChecksumCollection AnalyzerConfigDocuments => (ChecksumCollection)Children[8];
 
        public async Task FindAsync(
            ProjectState state,
            HashSet<Checksum> searchingChecksumsLeft,
            Dictionary<Checksum, object> result,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            // verify input
            Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksum));
            Contract.ThrowIfFalse(this == stateChecksum);
 
            if (searchingChecksumsLeft.Count == 0)
                return;
 
            if (searchingChecksumsLeft.Remove(Checksum))
            {
                result[Checksum] = this;
            }
 
            if (searchingChecksumsLeft.Remove(Info))
            {
                result[Info] = state.ProjectInfo.Attributes;
            }
 
            if (searchingChecksumsLeft.Remove(CompilationOptions))
            {
                Contract.ThrowIfNull(state.CompilationOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out.");
                result[CompilationOptions] = state.CompilationOptions;
            }
 
            if (searchingChecksumsLeft.Remove(ParseOptions))
            {
                Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out.");
                result[ParseOptions] = state.ParseOptions;
            }
 
            if (searchingChecksumsLeft.Remove(Documents.Checksum))
            {
                result[Documents.Checksum] = Documents;
            }
 
            if (searchingChecksumsLeft.Remove(ProjectReferences.Checksum))
            {
                result[ProjectReferences.Checksum] = ProjectReferences;
            }
 
            if (searchingChecksumsLeft.Remove(MetadataReferences.Checksum))
            {
                result[MetadataReferences.Checksum] = MetadataReferences;
            }
 
            if (searchingChecksumsLeft.Remove(AnalyzerReferences.Checksum))
            {
                result[AnalyzerReferences.Checksum] = AnalyzerReferences;
            }
 
            if (searchingChecksumsLeft.Remove(AdditionalDocuments.Checksum))
            {
                result[AdditionalDocuments.Checksum] = AdditionalDocuments;
            }
 
            if (searchingChecksumsLeft.Remove(AnalyzerConfigDocuments.Checksum))
            {
                result[AnalyzerConfigDocuments.Checksum] = AnalyzerConfigDocuments;
            }
 
            ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken);
            ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken);
            ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken);
 
            await ChecksumCollection.FindAsync(state.DocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false);
            await ChecksumCollection.FindAsync(state.AdditionalDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false);
            await ChecksumCollection.FindAsync(state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false);
        }
    }
 
    internal class DocumentStateChecksums : ChecksumWithChildren
    {
        public DocumentStateChecksums(Checksum infoChecksum, Checksum textChecksum)
            : this(ImmutableArray.Create<object>(infoChecksum, textChecksum))
        {
        }
 
        public DocumentStateChecksums(ImmutableArray<object> children) : base(children)
        {
        }
 
        public Checksum Info => (Checksum)Children[0];
        public Checksum Text => (Checksum)Children[1];
 
        public async Task FindAsync(
            TextDocumentState state,
            HashSet<Checksum> searchingChecksumsLeft,
            Dictionary<Checksum, object> result,
            CancellationToken cancellationToken)
        {
            Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum);
 
            cancellationToken.ThrowIfCancellationRequested();
 
            if (searchingChecksumsLeft.Remove(Checksum))
            {
                result[Checksum] = this;
            }
 
            if (searchingChecksumsLeft.Remove(Info))
            {
                result[Info] = state.Attributes;
            }
 
            if (searchingChecksumsLeft.Remove(Text))
            {
                result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false);
            }
        }
    }
 
    /// <summary>
    /// hold onto object checksum that currently doesn't have a place to hold onto checksum
    /// </summary>
    internal static class ChecksumCache
    {
        private static readonly ConditionalWeakTable<object, object> s_cache = new();
 
        public static IReadOnlyList<T> GetOrCreate<T>(IReadOnlyList<T> unorderedList, ConditionalWeakTable<object, object>.CreateValueCallback orderedListGetter)
            => (IReadOnlyList<T>)s_cache.GetValue(unorderedList, orderedListGetter);
 
        public static bool TryGetValue(object value, [NotNullWhen(true)] out Checksum? checksum)
        {
            // same key should always return same checksum
            if (!s_cache.TryGetValue(value, out var result))
            {
                checksum = null;
                return false;
            }
 
            checksum = (Checksum)result;
            return true;
        }
 
        public static Checksum GetOrCreate(object value, ConditionalWeakTable<object, object>.CreateValueCallback checksumCreator)
        {
            // same key should always return same checksum
            return (Checksum)s_cache.GetValue(value, checksumCreator);
        }
 
        public static T GetOrCreate<T>(object value, ConditionalWeakTable<object, object>.CreateValueCallback checksumCreator) where T : IChecksummedObject
        {
            // same key should always return same checksum
            return (T)s_cache.GetValue(value, checksumCreator);
        }
    }
}