File: Workspace\Solution\Solution.cs
Web Access
Project: src\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.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis;
 
/// <summary>
/// Represents a set of projects and their source code documents. 
/// </summary>
public partial class Solution
{
 
    // Values for all these are created on demand.
    private ImmutableDictionary<ProjectId, Project> _projectIdToProjectMap;
 
    /// <summary>
    /// Result of calling <see cref="WithFrozenPartialCompilationsAsync"/>.
    /// </summary>
    private readonly AsyncLazy<Solution> _cachedFrozenSolution;
 
    /// <summary>
    /// Mapping of DocumentId to the frozen solution we produced for it the last time we were queried.  This
    /// instance should be used as its own lock when reading or writing to it.
    /// </summary>
    private readonly Dictionary<DocumentId, AsyncLazy<Solution>> _documentIdToFrozenSolution = [];
 
    private Solution(
        SolutionCompilationState compilationState,
        AsyncLazy<Solution>? cachedFrozenSolution = null)
    {
        _projectIdToProjectMap = ImmutableDictionary<ProjectId, Project>.Empty;
        CompilationState = compilationState;
 
        _cachedFrozenSolution = cachedFrozenSolution ??
            AsyncLazy.Create(synchronousComputeFunction: static (self, c) =>
                self.ComputeFrozenSolution(c),
                this);
    }
 
    internal Solution(
        Workspace workspace,
        SolutionInfo.SolutionAttributes solutionAttributes,
        SolutionOptionSet options,
        IReadOnlyList<AnalyzerReference> analyzerReferences,
        ImmutableDictionary<string, StructuredAnalyzerConfigOptions> fallbackAnalyzerOptions)
        : this(new SolutionCompilationState(
                  new SolutionState(workspace.Kind, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences, fallbackAnalyzerOptions),
                  workspace.PartialSemanticsEnabled))
    {
    }
 
    internal SolutionState SolutionState => CompilationState.SolutionState;
 
    internal SolutionCompilationState CompilationState { get; }
 
    internal int WorkspaceVersion => this.SolutionState.WorkspaceVersion;
 
    internal bool PartialSemanticsEnabled => CompilationState.PartialSemanticsEnabled;
 
    /// <summary>
    /// Per solution services provided by the host environment.  Use this instead of <see
    /// cref="Workspace.Services"/> when possible.
    /// </summary>
    public SolutionServices Services => this.SolutionState.Services;
 
    internal string? WorkspaceKind => this.SolutionState.WorkspaceKind;
 
    internal ProjectState? GetProjectState(ProjectId projectId) => this.SolutionState.GetProjectState(projectId);
 
    /// <summary>
    /// The Workspace this solution is associated with.
    /// </summary>
    public Workspace Workspace
    {
        get
        {
            Contract.ThrowIfTrue(this.WorkspaceKind == CodeAnalysis.WorkspaceKind.RemoteWorkspace, "Access .Workspace off of a RemoteWorkspace Solution is not supported.");
#pragma warning disable CS0618 // Type or member is obsolete (TODO: obsolete the property)
            return this.SolutionState.Services.WorkspaceServices.Workspace;
#pragma warning restore
        }
    }
 
    /// <summary>
    /// The Id of the solution. Multiple solution instances may share the same Id.
    /// </summary>
    public SolutionId Id => this.SolutionState.Id;
 
    /// <summary>
    /// The path to the solution file or null if there is no solution file.
    /// </summary>
    public string? FilePath => this.SolutionState.FilePath;
 
    /// <summary>
    /// The solution version. This equates to the solution file's version.
    /// </summary>
    public VersionStamp Version => this.SolutionState.Version;
 
    /// <summary>
    /// A list of all the ids for all the projects contained by the solution.
    /// </summary>
    public IReadOnlyList<ProjectId> ProjectIds => this.SolutionState.ProjectIds;
 
    /// <summary>
    /// A list of all the projects contained by the solution.
    /// </summary>
    public IEnumerable<Project> Projects => ProjectIds.Select(id => GetProject(id)!);
 
    /// <summary>
    /// The version of the most recently modified project.
    /// </summary>
    public VersionStamp GetLatestProjectVersion() => this.SolutionState.GetLatestProjectVersion();
 
    /// <summary>
    /// True if the solution contains a project with the specified project ID.
    /// </summary>
    public bool ContainsProject([NotNullWhen(returnValue: true)] ProjectId? projectId)
        => this.SolutionState.ContainsProject(projectId);
 
    /// <summary>
    /// Gets the project in this solution with the specified project ID. 
    /// 
    /// If the id is not an id of a project that is part of this solution the method returns null.
    /// </summary>
    public Project? GetProject(ProjectId? projectId)
    {
        if (this.ContainsProject(projectId))
        {
            return ImmutableInterlocked.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this);
        }
 
        return null;
    }
 
    private static readonly Func<ProjectId, Solution, Project> s_createProjectFunction = CreateProject;
    private static Project CreateProject(ProjectId projectId, Solution solution)
    {
        var state = solution.SolutionState.GetProjectState(projectId);
        Contract.ThrowIfNull(state);
        return new Project(solution, state);
    }
 
#pragma warning disable IDE0060 // Remove unused parameter 'cancellationToken' - shipped public API
    /// <summary>
    /// Gets the <see cref="Project"/> associated with an assembly symbol.
    /// </summary>
    public Project? GetProject(IAssemblySymbol assemblySymbol,
        CancellationToken cancellationToken = default)
#pragma warning restore IDE0060 // Remove unused parameter
    {
        var projectId = SolutionCompilationState.GetProjectId(assemblySymbol);
        return GetProject(projectId);
    }
 
    /// <summary>
    /// Given a <paramref name="symbol"/> returns the <see cref="ProjectId"/> of the <see cref="Project"/> it came
    /// from.  Returns <see langword="null"/> if <paramref name="symbol"/> does not come from any project in this solution.
    /// </summary>
    /// <remarks>
    /// This function differs from <see cref="GetProject(IAssemblySymbol, CancellationToken)"/> in terms of how it
    /// treats <see cref="IAssemblySymbol"/>s.  Specifically, say there is the following:
    ///
    /// <c>
    /// Project-A, containing Symbol-A.<para/>
    /// Project-B, with a reference to Project-A, and usage of Symbol-A.
    /// </c>
    ///
    /// It is possible (with retargeting, and other complex cases) that Symbol-A from Project-B will be a different
    /// symbol than Symbol-A from Project-A.  However, <see cref="GetProject(IAssemblySymbol, CancellationToken)"/>
    /// will always try to return Project-A for either of the Symbol-A's, as it prefers to return the original
    /// Source-Project of the original definition, not the project that actually produced the symbol.  For many
    /// features this is an acceptable abstraction.  However, for some cases (Find-References in particular) it is
    /// necessary to resolve symbols back to the actual project/compilation that produced them for correctness.
    /// </remarks>
    internal ProjectId? GetOriginatingProjectId(ISymbol symbol)
        => CompilationState.GetOriginatingProjectInfo(symbol)?.ProjectId;
 
    /// <inheritdoc cref="GetOriginatingProjectId"/>
    internal Project? GetOriginatingProject(ISymbol symbol)
        => GetProject(GetOriginatingProjectId(symbol));
 
    /// <inheritdoc cref="GetOriginatingProjectId"/>
    /// <remarks>
    /// Returns the <see cref="Compilation"/> that produced the symbol.  In the case of a symbol that was retargetted
    /// this will be the compilation it was retargtted into, not the original compilation that it was retargetted from.
    /// </remarks>
    internal Compilation? GetOriginatingCompilation(ISymbol symbol)
        => CompilationState.GetOriginatingProjectInfo(symbol)?.Compilation;
 
    /// <summary>
    /// True if the solution contains the document in one of its projects
    /// </summary>
    public bool ContainsDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => this.SolutionState.ContainsDocument(documentId);
 
    /// <summary>
    /// True if the solution contains the additional document in one of its projects
    /// </summary>
    public bool ContainsAdditionalDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => this.SolutionState.ContainsAdditionalDocument(documentId);
 
    /// <summary>
    /// True if the solution contains the analyzer config document in one of its projects
    /// </summary>
    public bool ContainsAnalyzerConfigDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => this.SolutionState.ContainsAnalyzerConfigDocument(documentId);
 
    /// <summary>
    /// Gets the documentId in this solution with the specified syntax tree.
    /// </summary>
    public DocumentId? GetDocumentId(SyntaxTree? syntaxTree) => GetDocumentId(syntaxTree, projectId: null);
 
    /// <summary>
    /// Gets the documentId in this solution with the specified syntax tree.
    /// </summary>
    public DocumentId? GetDocumentId(SyntaxTree? syntaxTree, ProjectId? projectId)
        => CompilationState.GetDocumentState(syntaxTree, projectId)?.Id;
 
    /// <summary>
    /// Gets the document in this solution with the specified document ID.
    /// </summary>
    public Document? GetDocument(DocumentId? documentId)
        => GetProject(documentId?.ProjectId)?.GetDocument(documentId!);
 
    /// <summary>
    /// Gets a document or a source generated document in this solution with the specified document ID.
    /// </summary>
    internal ValueTask<Document?> GetDocumentAsync(DocumentId? documentId, bool includeSourceGenerated = false, CancellationToken cancellationToken = default)
    {
        var project = GetProject(documentId?.ProjectId);
        if (project == null)
        {
            return default;
        }
 
        Contract.ThrowIfNull(documentId);
        return project.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken);
    }
 
    /// <summary>
    /// Gets a document, additional document, analyzer config document or a source generated document in this solution with the specified document ID.
    /// </summary>
    internal ValueTask<TextDocument?> GetTextDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken = default)
    {
        var project = GetProject(documentId?.ProjectId);
        if (project == null)
        {
            return default;
        }
 
        Contract.ThrowIfNull(documentId);
        return project.GetTextDocumentAsync(documentId, cancellationToken);
    }
 
    /// <summary>
    /// Gets the additional document in this solution with the specified document ID.
    /// </summary>
    public TextDocument? GetAdditionalDocument(DocumentId? documentId)
    {
        if (this.ContainsAdditionalDocument(documentId))
        {
            return this.GetProject(documentId.ProjectId)!.GetAdditionalDocument(documentId);
        }
 
        return null;
    }
 
    /// <summary>
    /// Gets the analyzer config document in this solution with the specified document ID.
    /// </summary>
    public AnalyzerConfigDocument? GetAnalyzerConfigDocument(DocumentId? documentId)
    {
        if (this.ContainsAnalyzerConfigDocument(documentId))
        {
            return this.GetProject(documentId.ProjectId)!.GetAnalyzerConfigDocument(documentId);
        }
 
        return null;
    }
 
    public ValueTask<SourceGeneratedDocument?> GetSourceGeneratedDocumentAsync(DocumentId documentId, CancellationToken cancellationToken)
    {
        var project = GetProject(documentId.ProjectId);
 
        if (project == null)
        {
            return new(result: null);
        }
        else
        {
            return project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken);
        }
    }
 
    /// <summary>
    /// Gets the document in this solution with the specified syntax tree.
    /// </summary>
    public Document? GetDocument(SyntaxTree? syntaxTree)
        => this.GetDocument(syntaxTree, projectId: null);
 
    internal Document? GetDocument(SyntaxTree? syntaxTree, ProjectId? projectId)
    {
        if (syntaxTree != null)
        {
            var documentState = CompilationState.GetDocumentState(syntaxTree, projectId);
 
            if (documentState is SourceGeneratedDocumentState)
            {
                // We have the underlying state, but we need to get the wrapper SourceGeneratedDocument object. The wrapping is maintained by
                // the Project object, so we'll now fetch the project and ask it to get the SourceGeneratedDocument wrapper. Under the covers this
                // implicity may call to fetch the SourceGeneratedDocumentState a second time but that's not expensive.
                var generatedDocument = this.GetRequiredProject(documentState.Id.ProjectId).TryGetSourceGeneratedDocumentForAlreadyGeneratedId(documentState.Id);
                Contract.ThrowIfNull(generatedDocument, "The call to GetDocumentState found a SourceGeneratedDocumentState, so we should have found it now.");
                return generatedDocument;
            }
            else if (documentState is DocumentState)
            {
                return GetDocument(documentState.Id)!;
            }
        }
 
        return null;
    }
 
    private Solution WithCompilationState(SolutionCompilationState compilationState)
        => compilationState == CompilationState ? this : new Solution(compilationState);
 
    /// <summary>
    /// Creates a new solution instance that includes a project with the specified language and names.
    /// Returns the new project.
    /// </summary>
    public Project AddProject(string name, string assemblyName, string language)
    {
        var id = ProjectId.CreateNewId(debugName: name);
        return this.AddProject(id, name, assemblyName, language).GetProject(id)!;
    }
 
    /// <summary>
    /// Creates a new solution instance that includes a project with the specified language and names.
    /// </summary>
    public Solution AddProject(ProjectId projectId, string name, string assemblyName, string language)
        => this.AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), name, assemblyName, language));
 
    /// <inheritdoc cref="SolutionCompilationState.AddProjects"/>
    public Solution AddProject(ProjectInfo projectInfo)
    {
        using var _ = ArrayBuilder<ProjectInfo>.GetInstance(1, out var projectInfos);
        projectInfos.Add(projectInfo);
        return AddProjects(projectInfos);
    }
 
    /// <inheritdoc cref="SolutionCompilationState.AddProjects"/>
    internal Solution AddProjects(ArrayBuilder<ProjectInfo> projectInfos)
        => WithCompilationState(CompilationState.AddProjects(projectInfos));
 
    /// <inheritdoc cref="SolutionCompilationState.RemoveProjects"/>
    public Solution RemoveProject(ProjectId projectId)
    {
        using var _ = ArrayBuilder<ProjectId>.GetInstance(1, out var projectIds);
        projectIds.Add(projectId);
        return RemoveProjects(projectIds);
    }
 
    /// <inheritdoc cref="SolutionCompilationState.RemoveProjects"/>
    internal Solution RemoveProjects(ArrayBuilder<ProjectId> projectIds)
        => WithCompilationState(CompilationState.RemoveProjects(projectIds));
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the new
    /// assembly name.
    /// </summary>
    public Solution WithProjectAssemblyName(ProjectId projectId, string assemblyName)
    {
        CheckContainsProject(projectId);
 
        if (assemblyName == null)
        {
            throw new ArgumentNullException(nameof(assemblyName));
        }
 
        return WithCompilationState(CompilationState.WithProjectAssemblyName(projectId, assemblyName));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the output file path.
    /// </summary>
    public Solution WithProjectOutputFilePath(ProjectId projectId, string? outputFilePath)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectOutputFilePath(projectId, outputFilePath));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the reference assembly output file path.
    /// </summary>
    public Solution WithProjectOutputRefFilePath(ProjectId projectId, string? outputRefFilePath)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectOutputRefFilePath(projectId, outputRefFilePath));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the compiler output file path.
    /// </summary>
    public Solution WithProjectCompilationOutputInfo(ProjectId projectId, in CompilationOutputInfo info)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectCompilationOutputInfo(projectId, info));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the default namespace.
    /// </summary>
    public Solution WithProjectDefaultNamespace(ProjectId projectId, string? defaultNamespace)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectDefaultNamespace(projectId, defaultNamespace));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the specified attributes.
    /// </summary>
    internal Solution WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the name.
    /// </summary>
    public Solution WithProjectName(ProjectId projectId, string name)
    {
        CheckContainsProject(projectId);
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        return WithCompilationState(CompilationState.WithProjectName(projectId, name));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project specified updated to have the project file path.
    /// </summary>
    public Solution WithProjectFilePath(ProjectId projectId, string? filePath)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithProjectFilePath(projectId, filePath));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to have
    /// the specified compilation options.
    /// </summary>
    public Solution WithProjectCompilationOptions(ProjectId projectId, CompilationOptions options)
    {
        CheckContainsProject(projectId);
 
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
 
        return WithCompilationState(CompilationState.WithProjectCompilationOptions(projectId, options));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to have
    /// the specified parse options.
    /// </summary>
    public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions options)
    {
        CheckContainsProject(projectId);
 
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
 
        return WithCompilationState(CompilationState.WithProjectParseOptions(projectId, options));
    }
 
    /// <summary>
    /// Create a new solution instance updated to use the specified <see cref="FallbackAnalyzerOptions"/>.
    /// </summary>
    internal Solution WithFallbackAnalyzerOptions(ImmutableDictionary<string, StructuredAnalyzerConfigOptions> options)
        => WithCompilationState(CompilationState.WithFallbackAnalyzerOptions(options));
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to have
    /// the specified hasAllInformation.
    /// </summary>
    // TODO: https://github.com/dotnet/roslyn/issues/42449 make it public
    internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformation)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithHasAllInformation(projectId, hasAllInformation));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to have
    /// the specified runAnalyzers.
    /// </summary>
    // TODO: https://github.com/dotnet/roslyn/issues/42449 make it public
    internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithRunAnalyzers(projectId, runAnalyzers));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to have
    /// the specified hasSdkCodeStyleAnalyzers.
    /// </summary>
    internal Solution WithHasSdkCodeStyleAnalyzers(ProjectId projectId, bool hasSdkCodeStyleAnalyzers)
    {
        CheckContainsProject(projectId);
 
        return WithCompilationState(CompilationState.WithHasSdkCodeStyleAnalyzers(projectId, hasSdkCodeStyleAnalyzers));
    }
 
    /// <summary>
    /// Creates a new solution instance with the project documents in the order by the specified document ids.
    /// The specified document ids must be the same as what is already in the project; no adding or removing is allowed.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="documentIds"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="ArgumentException">The number of documents specified in <paramref name="documentIds"/> is not equal to the number of documents in project <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">Document specified in <paramref name="documentIds"/> does not exist in project <paramref name="projectId"/>.</exception>
    public Solution WithProjectDocumentsOrder(ProjectId projectId, ImmutableList<DocumentId> documentIds)
    {
        CheckContainsProject(projectId);
 
        if (documentIds == null)
        {
            throw new ArgumentNullException(nameof(documentIds));
        }
 
        return WithCompilationState(CompilationState.WithProjectDocumentsOrder(projectId, documentIds));
    }
 
    /// <summary>
    /// Updates the solution with project information stored in <paramref name="attributes"/>.
    /// </summary>
    internal Solution WithProjectAttributes(ProjectInfo.ProjectAttributes attributes)
    {
        CheckContainsProject(attributes.Id);
        return WithCompilationState(CompilationState.WithProjectAttributes(attributes));
    }
 
    /// <summary>
    /// Updates the solution with project information stored in <paramref name="info"/>.
    /// </summary>
    internal Solution WithProjectInfo(ProjectInfo info)
    {
        CheckContainsProject(info.Id);
        return WithCompilationState(CompilationState.WithProjectInfo(info));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include
    /// the specified project reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="projectReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project already references the target project.</exception>
    public Solution AddProjectReference(ProjectId projectId, ProjectReference projectReference)
    {
        return AddProjectReferences(projectId,
            [projectReference ?? throw new ArgumentNullException(nameof(projectReference))]);
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include
    /// the specified project references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="projectReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="projectReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project already references the target project.</exception>
    /// <exception cref="InvalidOperationException">Adding the project reference would create a circular dependency.</exception>
    public Solution AddProjectReferences(ProjectId projectId, IEnumerable<ProjectReference> projectReferences)
    {
        CheckContainsProject(projectId);
 
        // avoid enumerating multiple times:
        var collection = projectReferences?.ToCollection();
 
        PublicContract.RequireUniqueNonNullItems(collection, nameof(projectReferences));
 
        foreach (var projectReference in collection)
        {
            if (this.SolutionState.ContainsProjectReference(projectId, projectReference))
            {
                throw new InvalidOperationException(WorkspacesResources.The_project_already_references_the_target_project);
            }
        }
 
        CheckCircularProjectReferences(projectId, collection);
        CheckSubmissionProjectReferences(projectId, collection, ignoreExistingReferences: false);
 
        return WithCompilationState(CompilationState.AddProjectReferences(projectId, collection));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to no longer
    /// include the specified project reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="projectReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
    public Solution RemoveProjectReference(ProjectId projectId, ProjectReference projectReference)
    {
        try
        {
            if (projectReference == null)
                throw new ArgumentNullException(nameof(projectReference));
 
            CheckContainsProject(projectId);
 
            var oldProject = GetRequiredProjectState(projectId);
            if (!oldProject.ProjectReferences.Contains(projectReference))
                throw new ArgumentException(WorkspacesResources.Project_does_not_contain_specified_reference, nameof(projectReference));
 
            return WithCompilationState(CompilationState.RemoveProjectReference(projectId, projectReference));
        }
        catch (Exception ex) when (FatalError.ReportAndPropagate(ex))
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to contain
    /// the specified list of project references.
    /// </summary>
    /// <param name="projectId">Id of the project whose references to replace with <paramref name="projectReferences"/>.</param>
    /// <param name="projectReferences">New project references.</param>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="projectReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="projectReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    public Solution WithProjectReferences(ProjectId projectId, IEnumerable<ProjectReference>? projectReferences)
    {
        CheckContainsProject(projectId);
 
        // avoid enumerating multiple times:
        var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(projectReferences, nameof(projectReferences));
 
        CheckCircularProjectReferences(projectId, collection);
        CheckSubmissionProjectReferences(projectId, collection, ignoreExistingReferences: true);
 
        return WithCompilationState(CompilationState.WithProjectReferences(projectId, collection));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include the 
    /// specified metadata reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="metadataReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
    public Solution AddMetadataReference(ProjectId projectId, MetadataReference metadataReference)
    {
        return AddMetadataReferences(projectId,
            [metadataReference ?? throw new ArgumentNullException(nameof(metadataReference))]);
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include the
    /// specified metadata references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="metadataReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="metadataReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
    public Solution AddMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
    {
        CheckContainsProject(projectId);
 
        // avoid enumerating multiple times:
        var collection = metadataReferences?.ToCollection();
 
        PublicContract.RequireUniqueNonNullItems(collection, nameof(metadataReferences));
        foreach (var metadataReference in collection)
        {
            if (this.SolutionState.ContainsMetadataReference(projectId, metadataReference))
            {
                throw new InvalidOperationException(WorkspacesResources.The_project_already_contains_the_specified_reference);
            }
        }
 
        return WithCompilationState(CompilationState.AddMetadataReferences(projectId, collection));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to no longer include
    /// the specified metadata reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="metadataReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project does not contain the specified reference.</exception>
    public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference metadataReference)
    {
        CheckContainsProject(projectId);
 
        if (metadataReference == null)
            throw new ArgumentNullException(nameof(metadataReference));
 
        var oldProject = GetRequiredProjectState(projectId);
        if (!oldProject.MetadataReferences.Contains(metadataReference))
            throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference);
 
        return WithCompilationState(CompilationState.RemoveMetadataReference(projectId, metadataReference));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include only the
    /// specified metadata references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="metadataReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="metadataReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
    {
        CheckContainsProject(projectId);
 
        var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(metadataReferences, nameof(metadataReferences));
 
        return WithCompilationState(CompilationState.WithProjectMetadataReferences(projectId, collection));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include the 
    /// specified analyzer reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    public Solution AddAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference)
    {
        return AddAnalyzerReferences(projectId,
            [analyzerReference ?? throw new ArgumentNullException(nameof(analyzerReference))]);
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include the
    /// specified analyzer references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
    public Solution AddAnalyzerReferences(ProjectId projectId, IEnumerable<AnalyzerReference> analyzerReferences)
    {
        CheckContainsProject(projectId);
 
        if (analyzerReferences is null)
            throw new ArgumentNullException(nameof(analyzerReferences));
 
        var collection = analyzerReferences.ToImmutableArray();
 
        PublicContract.RequireUniqueNonNullItems(collection, nameof(analyzerReferences));
 
        foreach (var analyzerReference in collection)
        {
            if (this.SolutionState.ContainsAnalyzerReference(projectId, analyzerReference))
                throw new InvalidOperationException(WorkspacesResources.The_project_already_contains_the_specified_reference);
        }
 
        var boxedReferences = Roslyn.Utilities.EnumerableExtensions.ToBoxedImmutableArray([
            // Note: we guaranteed that analyzerReferences has no duplicates, and has no overlap with the existing
            // analyzer references above, so we can just concatenate them here safely.
            .. this.GetRequiredProjectState(projectId).AnalyzerReferences,
            .. collection,
        ]);
        return WithCompilationState(CompilationState.WithProjectAnalyzerReferences(projectId, boxedReferences));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to no longer include
    /// the specified analyzer reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    /// <exception cref="InvalidOperationException">The project does not contain the specified reference.</exception>
    public Solution RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference)
    {
        CheckContainsProject(projectId);
 
        if (analyzerReference == null)
            throw new ArgumentNullException(nameof(analyzerReference));
 
        var oldProject = GetRequiredProjectState(projectId);
        if (!oldProject.AnalyzerReferences.Contains(analyzerReference))
            throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference);
 
        var builder = new FixedSizeArrayBuilder<AnalyzerReference>(oldProject.AnalyzerReferences.Count - 1);
        foreach (var reference in oldProject.AnalyzerReferences)
        {
            if (!reference.Equals(analyzerReference))
                builder.Add(reference);
        }
 
        return WithCompilationState(CompilationState.WithProjectAnalyzerReferences(projectId, builder.MoveToImmutable()));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to include only the
    /// specified analyzer references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
    public Solution WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable<AnalyzerReference> analyzerReferences)
    {
        CheckContainsProject(projectId);
 
        var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences));
 
        return WithCompilationState(CompilationState.WithProjectAnalyzerReferences(projectId, collection));
    }
 
    /// <summary>
    /// Create a new solution instance updated to include the specified analyzer reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
    public Solution AddAnalyzerReference(AnalyzerReference analyzerReference)
    {
        return AddAnalyzerReferences(
            [analyzerReference ?? throw new ArgumentNullException(nameof(analyzerReference))]);
    }
 
    /// <summary>
    /// Create a new solution instance updated to include the specified analyzer references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
    /// <exception cref="InvalidOperationException">The solution already contains the specified reference.</exception>
    public Solution AddAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
    {
        // avoid enumerating multiple times:
        var collection = analyzerReferences?.ToCollection();
 
        PublicContract.RequireUniqueNonNullItems(collection, nameof(analyzerReferences));
 
        foreach (var analyzerReference in collection)
        {
            if (this.SolutionState.AnalyzerReferences.Contains(analyzerReference))
            {
                throw new InvalidOperationException(WorkspacesResources.The_solution_already_contains_the_specified_reference);
            }
        }
 
        return WithCompilationState(CompilationState.AddAnalyzerReferences(collection));
    }
 
    /// <summary>
    /// Create a new solution instance with the project specified updated to no longer include
    /// the specified analyzer reference.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The solution does not contain the specified reference.</exception>
    public Solution RemoveAnalyzerReference(AnalyzerReference analyzerReference)
    {
        if (analyzerReference == null)
            throw new ArgumentNullException(nameof(analyzerReference));
 
        if (!this.SolutionState.AnalyzerReferences.Contains(analyzerReference))
            throw new InvalidOperationException(WorkspacesResources.Solution_does_not_contain_specified_reference);
 
        return WithCompilationState(CompilationState.RemoveAnalyzerReference(analyzerReference));
    }
 
    /// <summary>
    /// Creates a new solution instance with the specified analyzer references.
    /// </summary>
    /// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
    public Solution WithAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
    {
        var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences));
 
        return WithCompilationState(CompilationState.WithAnalyzerReferences(collection));
    }
 
    private static SourceCodeKind GetSourceCodeKind(ProjectState project)
        => project.ParseOptions != null ? project.ParseOptions.Kind : SourceCodeKind.Regular;
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// document instance defined by its name and text.
    /// </summary>
    public Solution AddDocument(DocumentId documentId, string name, string text, IEnumerable<string>? folders = null, string? filePath = null)
    {
        if (documentId == null)
            throw new ArgumentNullException(nameof(documentId));
 
        if (name == null)
            throw new ArgumentNullException(nameof(name));
 
        var project = GetRequiredProjectState(documentId.ProjectId);
        var sourceText = SourceText.From(text, encoding: null, checksumAlgorithm: project.ChecksumAlgorithm);
 
        return AddDocumentImpl(project, documentId, name, sourceText, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated: false);
    }
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// document instance defined by its name and text.
    /// </summary>
    public Solution AddDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null, bool isGenerated = false)
    {
        if (documentId == null)
            throw new ArgumentNullException(nameof(documentId));
 
        if (name == null)
            throw new ArgumentNullException(nameof(name));
 
        if (text == null)
            throw new ArgumentNullException(nameof(text));
 
        var project = GetRequiredProjectState(documentId.ProjectId);
        return AddDocumentImpl(project, documentId, name, text, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated);
    }
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// document instance defined by its name and root <see cref="SyntaxNode"/>.
    /// </summary>
    public Solution AddDocument(DocumentId documentId, string name, SyntaxNode syntaxRoot, IEnumerable<string>? folders = null, string? filePath = null, bool isGenerated = false, PreservationMode preservationMode = PreservationMode.PreserveValue)
    {
        if (documentId == null)
            throw new ArgumentNullException(nameof(documentId));
 
        if (name == null)
            throw new ArgumentNullException(nameof(name));
 
        if (syntaxRoot == null)
            throw new ArgumentNullException(nameof(syntaxRoot));
 
        var project = GetRequiredProjectState(documentId.ProjectId);
        var sourceText = SourceText.From(string.Empty, encoding: null, project.ChecksumAlgorithm);
 
        return AddDocumentImpl(project, documentId, name, sourceText, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated).
            WithDocumentSyntaxRoot(documentId, syntaxRoot, preservationMode);
    }
 
    private Solution AddDocumentImpl(ProjectState project, DocumentId documentId, string name, SourceText text, IReadOnlyList<string>? folders, string? filePath, bool isGenerated)
        => AddDocument(DocumentInfo.Create(
            documentId,
            name: name,
            folders: folders,
            sourceCodeKind: GetSourceCodeKind(project),
            loader: TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), name)),
            filePath: filePath,
            isGenerated: isGenerated));
 
    /// <summary>
    /// Creates a new solution instance with the project updated to include a new document with
    /// the arguments specified.
    /// </summary>
    public Solution AddDocument(DocumentId documentId, string name, TextLoader loader, IEnumerable<string>? folders = null)
    {
        if (documentId == null)
            throw new ArgumentNullException(nameof(documentId));
 
        if (name == null)
            throw new ArgumentNullException(nameof(name));
 
        if (loader == null)
            throw new ArgumentNullException(nameof(loader));
 
        var project = GetRequiredProjectState(documentId.ProjectId);
 
        return AddDocument(DocumentInfo.Create(
            documentId,
            name,
            folders,
            GetSourceCodeKind(project),
            loader));
    }
 
    /// <summary>
    /// Create a new solution instance with the corresponding project updated to include a new 
    /// document instanced defined by the document info.
    /// </summary>
    public Solution AddDocument(DocumentInfo documentInfo)
        => AddDocuments([documentInfo]);
 
    /// <summary>
    /// Create a new <see cref="Solution"/> instance with the corresponding <see cref="Project"/>s updated to include
    /// the documents specified by <paramref name="documentInfos"/>.
    /// </summary>
    /// <returns>A new <see cref="Solution"/> with the documents added.</returns>
    public Solution AddDocuments(ImmutableArray<DocumentInfo> documentInfos)
        => WithCompilationState(CompilationState.AddDocumentsToMultipleProjects<DocumentState>(documentInfos));
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// additional document instance defined by its name and text.
    /// </summary>
    public Solution AddAdditionalDocument(DocumentId documentId, string name, string text, IEnumerable<string>? folders = null, string? filePath = null)
        => this.AddAdditionalDocument(documentId, name, SourceText.From(text), folders, filePath);
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// additional document instance defined by its name and text.
    /// </summary>
    public Solution AddAdditionalDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null)
    {
        if (documentId == null)
        {
            throw new ArgumentNullException(nameof(documentId));
        }
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }
 
        var info = CreateDocumentInfo(documentId, name, text, folders, filePath);
        return this.AddAdditionalDocument(info);
    }
 
    public Solution AddAdditionalDocument(DocumentInfo documentInfo)
        => AddAdditionalDocuments([documentInfo]);
 
    public Solution AddAdditionalDocuments(ImmutableArray<DocumentInfo> documentInfos)
        => WithCompilationState(CompilationState.AddDocumentsToMultipleProjects<AdditionalDocumentState>(documentInfos));
 
    /// <summary>
    /// Creates a new solution instance with the corresponding project updated to include a new
    /// analyzer config document instance defined by its name and text.
    /// </summary>
    public Solution AddAnalyzerConfigDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null)
    {
        if (documentId == null)
        {
            throw new ArgumentNullException(nameof(documentId));
        }
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }
 
        // TODO: should we validate file path?
        // https://github.com/dotnet/roslyn/issues/41940
 
        var info = CreateDocumentInfo(documentId, name, text, folders, filePath);
        return this.AddAnalyzerConfigDocuments([info]);
    }
 
    private DocumentInfo CreateDocumentInfo(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders, string? filePath)
    {
        var project = GetRequiredProjectState(documentId.ProjectId);
        var version = VersionStamp.Create();
        var loader = TextLoader.From(TextAndVersion.Create(text, version, name));
 
        return DocumentInfo.Create(
            documentId,
            name: name,
            folders: folders,
            sourceCodeKind: GetSourceCodeKind(project),
            loader: loader,
            filePath: filePath);
    }
 
    internal ProjectState GetRequiredProjectState(ProjectId projectId)
        => this.SolutionState.GetProjectState(projectId) ?? throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, projectId));
 
    /// <summary>
    /// Creates a new Solution instance that contains a new compiler configuration document like a .editorconfig file.
    /// </summary>
    public Solution AddAnalyzerConfigDocuments(ImmutableArray<DocumentInfo> documentInfos)
        => WithCompilationState(CompilationState.AddDocumentsToMultipleProjects<AnalyzerConfigDocumentState>(documentInfos));
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified document.
    /// </summary>
    public Solution RemoveDocument(DocumentId documentId)
    {
        CheckContainsDocument(documentId);
        return RemoveDocumentsImpl([documentId]);
    }
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified documents.
    /// </summary>
    public Solution RemoveDocuments(ImmutableArray<DocumentId> documentIds)
    {
        CheckContainsDocuments(documentIds);
        return RemoveDocumentsImpl(documentIds);
    }
 
    private Solution RemoveDocumentsImpl(ImmutableArray<DocumentId> documentIds)
        => WithCompilationState(CompilationState.RemoveDocumentsFromMultipleProjects<DocumentState>(documentIds));
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified additional document.
    /// </summary>
    public Solution RemoveAdditionalDocument(DocumentId documentId)
    {
        CheckContainsAdditionalDocument(documentId);
        return RemoveAdditionalDocumentsImpl([documentId]);
    }
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified additional documents.
    /// </summary>
    public Solution RemoveAdditionalDocuments(ImmutableArray<DocumentId> documentIds)
    {
        CheckContainsAdditionalDocuments(documentIds);
        return RemoveAdditionalDocumentsImpl(documentIds);
    }
 
    private Solution RemoveAdditionalDocumentsImpl(ImmutableArray<DocumentId> documentIds)
        => WithCompilationState(CompilationState.RemoveDocumentsFromMultipleProjects<AdditionalDocumentState>(documentIds));
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>.
    /// </summary>
    public Solution RemoveAnalyzerConfigDocument(DocumentId documentId)
    {
        CheckContainsAnalyzerConfigDocument(documentId);
        return RemoveAnalyzerConfigDocumentsImpl([documentId]);
    }
 
    /// <summary>
    /// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>s.
    /// </summary>
    public Solution RemoveAnalyzerConfigDocuments(ImmutableArray<DocumentId> documentIds)
    {
        CheckContainsAnalyzerConfigDocuments(documentIds);
        return RemoveAnalyzerConfigDocumentsImpl(documentIds);
    }
 
    private Solution RemoveAnalyzerConfigDocumentsImpl(ImmutableArray<DocumentId> documentIds)
        => WithCompilationState(CompilationState.RemoveDocumentsFromMultipleProjects<AnalyzerConfigDocumentState>(documentIds));
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the new name.
    /// </summary>
    public Solution WithDocumentName(DocumentId documentId, string name)
    {
        CheckContainsDocument(documentId);
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        return WithCompilationState(CompilationState.WithDocumentAttributes(
            documentId,
            name,
            static (attributes, value) => attributes.With(name: value)));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to be contained in
    /// the sequence of logical folders.
    /// </summary>
    public Solution WithDocumentFolders(DocumentId documentId, IEnumerable<string>? folders)
    {
        CheckContainsDocument(documentId);
 
        var collection = PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders));
 
        return WithCompilationState(CompilationState.WithDocumentAttributes(
            documentId,
            collection,
            static (attributes, value) => attributes.With(folders: value)));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the specified file path.
    /// </summary>
    public Solution WithDocumentFilePath(DocumentId documentId, string? filePath)
    {
        CheckContainsDocument(documentId);
 
        return WithCompilationState(CompilationState.WithDocumentAttributes(
            documentId,
            filePath,
            static (attributes, value) => attributes.With(filePath: value)));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the text
    /// specified.
    /// </summary>
    public Solution WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
        => WithDocumentTexts([(documentId, text)], mode);
 
    internal Solution WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text)> texts, PreservationMode mode = PreservationMode.PreserveValue)
    {
        foreach (var (documentId, text) in texts)
        {
            CheckContainsDocument(documentId);
 
            if (text == null)
                throw new ArgumentNullException(nameof(text));
 
            if (!mode.IsValid())
                throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithDocumentTexts(texts, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the additional document specified updated to have the text
    /// specified.
    /// </summary>
    public Solution WithAdditionalDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
    {
        CheckContainsAdditionalDocument(documentId);
 
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithAdditionalDocumentText(documentId, text, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the analyzer config document specified updated to have the text
    /// supplied by the text loader.
    /// </summary>
    public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
    {
        CheckContainsAnalyzerConfigDocument(documentId);
 
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithAnalyzerConfigDocumentText(documentId, text, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the text
    /// and version specified.
    /// </summary>
    public Solution WithDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
    {
        CheckContainsDocument(documentId);
 
        if (textAndVersion == null)
        {
            throw new ArgumentNullException(nameof(textAndVersion));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithDocumentText(documentId, textAndVersion, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the additional document specified updated to have the text
    /// and version specified.
    /// </summary>
    public Solution WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
    {
        CheckContainsAdditionalDocument(documentId);
 
        if (textAndVersion == null)
        {
            throw new ArgumentNullException(nameof(textAndVersion));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithAdditionalDocumentText(documentId, textAndVersion, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the analyzer config document specified updated to have the text
    /// and version specified.
    /// </summary>
    public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
    {
        CheckContainsAnalyzerConfigDocument(documentId);
 
        if (textAndVersion == null)
        {
            throw new ArgumentNullException(nameof(textAndVersion));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have a syntax tree
    /// rooted by the specified syntax node.
    /// </summary>
    public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue)
        => WithDocumentSyntaxRoots([(documentId, root)], mode);
 
    /// <inheritdoc cref="WithDocumentSyntaxRoot"/>.
    internal Solution WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root)> syntaxRoots, PreservationMode mode = PreservationMode.PreserveValue)
    {
        if (!mode.IsValid())
            throw new ArgumentOutOfRangeException(nameof(mode));
 
        foreach (var (documentId, root) in syntaxRoots)
        {
            CheckContainsDocument(documentId);
 
            if (root == null)
                throw new ArgumentNullException(nameof(root));
        }
 
        return WithCompilationState(CompilationState.WithDocumentSyntaxRoots(syntaxRoots, mode));
    }
 
    internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState, bool forceEvenIfTreesWouldDiffer)
        => WithCompilationState(CompilationState.WithDocumentContentsFrom([(documentId, documentState)], forceEvenIfTreesWouldDiffer));
 
    internal Solution WithDocumentContentsFrom(ImmutableArray<(DocumentId documentId, DocumentState documentState)> documentIdsAndStates, bool forceEvenIfTreesWouldDiffer)
        => WithCompilationState(CompilationState.WithDocumentContentsFrom(documentIdsAndStates, forceEvenIfTreesWouldDiffer));
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the source
    /// code kind specified.
    /// </summary>
    public Solution WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind sourceCodeKind)
    {
        CheckContainsDocument(documentId);
 
#pragma warning disable CS0618 // Interactive is obsolete but this method accepts it for backward compatibility.
        if (sourceCodeKind == SourceCodeKind.Interactive)
        {
            sourceCodeKind = SourceCodeKind.Script;
        }
#pragma warning restore CS0618
 
        if (!sourceCodeKind.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(sourceCodeKind));
        }
 
        return WithCompilationState(CompilationState.WithDocumentSourceCodeKind(documentId, sourceCodeKind));
    }
 
    /// <summary>
    /// Creates a new solution instance with the document specified updated to have the text
    /// supplied by the text loader.
    /// </summary>
    public Solution WithDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
    {
        CheckContainsDocument(documentId);
 
        if (loader == null)
        {
            throw new ArgumentNullException(nameof(loader));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.UpdateDocumentTextLoader(documentId, loader, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the additional document specified updated to have the text
    /// supplied by the text loader.
    /// </summary>
    public Solution WithAdditionalDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
    {
        CheckContainsAdditionalDocument(documentId);
 
        if (loader == null)
        {
            throw new ArgumentNullException(nameof(loader));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.UpdateAdditionalDocumentTextLoader(documentId, loader, mode));
    }
 
    /// <summary>
    /// Creates a new solution instance with the analyzer config document specified updated to have the text
    /// supplied by the text loader.
    /// </summary>
    public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
    {
        CheckContainsAnalyzerConfigDocument(documentId);
 
        if (loader == null)
        {
            throw new ArgumentNullException(nameof(loader));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode));
    }
 
    /// <summary>
    /// Returns a solution instance where every project is frozen at whatever current state it is in
    /// </summary>
    /// <param name="cancellationToken"></param>
    internal Solution WithFrozenPartialCompilations(CancellationToken cancellationToken)
        => _cachedFrozenSolution.GetValue(cancellationToken);
 
    /// <inheritdoc cref="WithFrozenPartialCompilations"/>
    internal Task<Solution> WithFrozenPartialCompilationsAsync(CancellationToken cancellationToken)
        => _cachedFrozenSolution.GetValueAsync(cancellationToken);
 
    private Solution ComputeFrozenSolution(CancellationToken cancellationToken)
    {
        // in progress solutions are disabled for some testing
        if (this.Services.GetService<IWorkspacePartialSolutionsTestHook>()?.IsPartialSolutionDisabled == true)
            return this;
 
        var newCompilationState = this.CompilationState.WithFrozenPartialCompilations(cancellationToken);
 
        var frozenSolution = new Solution(
            newCompilationState,
            // Set the frozen solution to be its own frozen solution.  Freezing multiple times is a no-op.
            cachedFrozenSolution: _cachedFrozenSolution);
 
        return frozenSolution;
    }
 
    /// <summary>
    /// Creates a branch of the solution that has its compilations frozen in whatever state they are in at the time,
    /// assuming a background compiler is busy building this compilations.
    /// <para/> A compilation for the project containing the specified document id will be guaranteed to exist with
    /// at least the syntax tree for the document.
    /// <para/> This not intended to be the public API, use Document.WithFrozenPartialSemantics() instead.
    /// </summary>
    internal Solution WithFrozenPartialCompilationIncludingSpecificDocument(DocumentId documentId, CancellationToken cancellationToken)
    {
        return GetLazySolution().GetValue(cancellationToken);
 
        AsyncLazy<Solution> GetLazySolution()
        {
            lock (_documentIdToFrozenSolution)
            {
                if (!_documentIdToFrozenSolution.TryGetValue(documentId, out var lazySolution))
                {
                    // in a local function to prevent lambda allocations when not needed.
                    lazySolution = CreateLazyFrozenSolution(this.CompilationState, documentId);
                    _documentIdToFrozenSolution.Add(documentId, lazySolution);
                }
 
                return lazySolution;
            }
        }
 
        static AsyncLazy<Solution> CreateLazyFrozenSolution(SolutionCompilationState compilationState, DocumentId documentId)
            => AsyncLazy.Create(synchronousComputeFunction: static (arg, cancellationToken) =>
                ComputeFrozenSolution(arg.compilationState, arg.documentId, cancellationToken),
                arg: (compilationState, documentId));
 
        static Solution ComputeFrozenSolution(SolutionCompilationState compilationState, DocumentId documentId, CancellationToken cancellationToken)
        {
            var newCompilationState = compilationState.WithFrozenPartialCompilationIncludingSpecificDocument(documentId, cancellationToken);
            var solution = new Solution(newCompilationState);
 
            // ensure that this document is within the frozen-partial-document for the solution we're creating.  That
            // way, if we ask to freeze it again, we'll just the same document back.
            Contract.ThrowIfTrue(solution._documentIdToFrozenSolution.Count != 0);
            solution._documentIdToFrozenSolution.Add(documentId, AsyncLazy.Create(solution));
 
            return solution;
        }
    }
 
    internal async Task<Solution> WithMergedLinkedFileChangesAsync(
        Solution oldSolution,
        SolutionChanges? solutionChanges = null,
        IMergeConflictHandler? mergeConflictHandler = null,
        CancellationToken cancellationToken = default)
    {
        // we only log sessioninfo for actual changes committed to workspace which should exclude ones from preview
        var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution));
 
        return (await session.MergeDiffsAsync(mergeConflictHandler, cancellationToken).ConfigureAwait(false)).MergedSolution;
    }
 
    internal ImmutableArray<DocumentId> GetRelatedDocumentIds(DocumentId documentId, bool includeDifferentLanguages = false)
        => this.SolutionState.GetRelatedDocumentIds(documentId, includeDifferentLanguages);
 
    /// <summary>
    /// Returns one of any of the related documents of <paramref name="documentId"/>.  Importantly, this will never
    /// return <paramref name="documentId"/> (unlike <see cref="GetRelatedDocumentIds"/> which includes the original
    /// file in the result).
    /// </summary>
    /// <param name="relatedProjectIdHint">A hint on the first project to search when looking for related
    /// documents.  Must not be the project that <paramref name="documentId"/> is from.</param>
    internal DocumentId? GetFirstRelatedDocumentId(DocumentId documentId, ProjectId? relatedProjectIdHint)
        => this.SolutionState.GetFirstRelatedDocumentId(documentId, relatedProjectIdHint);
 
    internal Solution WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services)
        => WithCompilationState(CompilationState.WithNewWorkspace(workspaceKind, workspaceVersion, services));
 
    /// <summary>
    /// Formerly, returned a copy of the solution isolated from the original so that they do not share computed state. It now does nothing.
    /// </summary>
    [Obsolete("This method no longer produces a Solution that does not share state and is no longer necessary to call.", error: false)]
    [EditorBrowsable(EditorBrowsableState.Never)] // hide this since it is obsolete and only leads to confusion
    public Solution GetIsolatedSolution()
    {
        // To maintain compat, just return ourself, which will be functionally identical.
        return this;
    }
 
    /// <summary>
    /// Creates a new solution instance with all the documents specified updated to have the same specified text.
    /// </summary>
    public Solution WithDocumentText(IEnumerable<DocumentId?> documentIds, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
    {
        if (documentIds == null)
        {
            throw new ArgumentNullException(nameof(documentIds));
        }
 
        if (text == null)
        {
            throw new ArgumentNullException(nameof(text));
        }
 
        if (!mode.IsValid())
        {
            throw new ArgumentOutOfRangeException(nameof(mode));
        }
 
        return WithCompilationState(CompilationState.WithDocumentText(documentIds, text, mode));
    }
 
    /// <summary>
    /// Returns a new Solution that will always produce a specific output for a generated file. This is used only in the
    /// implementation of <see cref="TextExtensions.GetOpenDocumentInCurrentContextWithChanges"/> where if a user has a source
    /// generated file open, we need to make sure everything lines up.
    /// </summary>
    internal Document WithFrozenSourceGeneratedDocument(
        SourceGeneratedDocumentIdentity documentIdentity, DateTime generationDateTime, SourceText text)
    {
        var newCompilationState = CompilationState.WithFrozenSourceGeneratedDocuments([(documentIdentity, generationDateTime, text)]);
        var newSolution = WithCompilationState(newCompilationState);
 
        var newDocumentState = newCompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId);
        Contract.ThrowIfNull(newDocumentState, "Because we just froze this document, it should always exist.");
 
        var newProject = newSolution.GetRequiredProject(newDocumentState.Id.ProjectId);
        return newProject.GetOrCreateSourceGeneratedDocument(newDocumentState);
    }
 
    internal Solution WithFrozenSourceGeneratedDocuments(ImmutableArray<(SourceGeneratedDocumentIdentity documentIdentity, DateTime generationDateTime, SourceText text)> documents)
        => WithCompilationState(CompilationState.WithFrozenSourceGeneratedDocuments(documents));
 
    /// <inheritdoc cref="SolutionCompilationState.UpdateSpecificSourceGeneratorExecutionVersions"/>
    internal Solution UpdateSpecificSourceGeneratorExecutionVersions(SourceGeneratorExecutionVersionMap sourceGeneratorExecutionVersionMap)
        => WithCompilationState(CompilationState.UpdateSpecificSourceGeneratorExecutionVersions(sourceGeneratorExecutionVersionMap));
 
    /// <summary>
    /// Undoes the operation of <see cref="WithFrozenSourceGeneratedDocument"/>; any frozen source generated document is allowed
    /// to have it's real output again.
    /// </summary>
    internal Solution WithoutFrozenSourceGeneratedDocuments()
        => WithCompilationState(CompilationState.WithoutFrozenSourceGeneratedDocuments());
 
    /// <summary>
    /// Returns a new Solution which represents the same state as before, but with the cached generator driver state from the given project updated to match.
    /// </summary>
    /// <remarks>
    /// When generators are ran in a Solution snapshot, they may cache state to speed up future runs. For Razor, we only run their generator on forked
    /// solutions that are thrown away; this API gives us a way to reuse that cached state in other forked solutions, since otherwise there's no way to reuse
    /// the cached state.
    /// </remarks>
    internal Solution WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState)
        => WithCompilationState(CompilationState.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState));
 
    /// <summary>
    /// Gets an objects that lists the added, changed and removed projects between
    /// this solution and the specified solution.
    /// </summary>
    public SolutionChanges GetChanges(Solution oldSolution)
    {
        if (oldSolution == null)
        {
            throw new ArgumentNullException(nameof(oldSolution));
        }
 
        return new SolutionChanges(this, oldSolution);
    }
 
    /// <summary>
    /// Gets the set of <see cref="DocumentId"/>s in this <see cref="Solution"/> with a
    /// <see cref="TextDocument.FilePath"/> that matches the given file path. This may return IDs for any type of document
    /// including <see cref="AdditionalDocument"/>s or <see cref="AnalyzerConfigDocument" />s.
    /// </summary>
    /// <remarks>
    /// It's possible (but unlikely) that the same file may exist as more than one type of document in the same solution. If this
    /// were to return more than one <see cref="DocumentId"/>, you should not assume that just because one is a regular source file means
    /// that all of them would be.
    /// </remarks>
    public ImmutableArray<DocumentId> GetDocumentIdsWithFilePath(string? filePath) => this.SolutionState.GetDocumentIdsWithFilePath(filePath);
 
    /// <summary>
    /// Gets a <see cref="ProjectDependencyGraph"/> that details the dependencies between projects for this solution.
    /// </summary>
    public ProjectDependencyGraph GetProjectDependencyGraph() => this.SolutionState.GetProjectDependencyGraph();
 
    /// <summary>
    /// Returns the options that should be applied to this solution. This is equivalent to <see cref="Workspace.Options" /> when the <see cref="Solution"/> 
    /// instance was created.
    /// </summary>
    public OptionSet Options => this.SolutionState.Options;
 
    /// <summary>
    /// Analyzer references associated with the solution.
    /// </summary>
    public IReadOnlyList<AnalyzerReference> AnalyzerReferences => this.SolutionState.AnalyzerReferences;
 
    /// <summary>
    /// Fallback analyzer config options by language. The set of languages does not need to match the set of languages of projects included in the current solution snapshot
    /// since these options can be updated independently of the projects contained in the solution.
    /// Generally, the host is responsible for keeping these options up-to-date with whatever option store it maintains
    /// and for making sure fallback options are available in the solution for all languages the host supports.
    /// </summary>
    internal ImmutableDictionary<string, StructuredAnalyzerConfigOptions> FallbackAnalyzerOptions => SolutionState.FallbackAnalyzerOptions;
 
    /// <summary>
    /// Creates a new solution instance with the specified <paramref name="options"/>.
    /// </summary>
    public Solution WithOptions(OptionSet options)
    {
        return options switch
        {
            SolutionOptionSet serializableOptions => WithOptions(serializableOptions),
            null => throw new ArgumentNullException(nameof(options)),
            _ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(options))
        };
    }
 
    /// <summary>
    /// Creates a new solution instance with the specified serializable <paramref name="options"/>.
    /// </summary>
    internal Solution WithOptions(SolutionOptionSet options)
        => WithCompilationState(CompilationState.WithOptions(options));
 
    private void CheckContainsProject(ProjectId projectId)
    {
        if (projectId == null)
        {
            throw new ArgumentNullException(nameof(projectId));
        }
 
        if (!ContainsProject(projectId))
        {
            throw new InvalidOperationException(WorkspacesResources.The_solution_does_not_contain_the_specified_project);
        }
    }
 
    private void CheckContainsDocument(DocumentId documentId)
    {
        if (documentId == null)
        {
            throw new ArgumentNullException(nameof(documentId));
        }
 
        if (!ContainsDocument(documentId))
        {
            throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
        }
    }
 
    private void CheckContainsDocuments(ImmutableArray<DocumentId> documentIds)
    {
        if (documentIds.IsDefault)
        {
            throw new ArgumentNullException(nameof(documentIds));
        }
 
        foreach (var documentId in documentIds)
        {
            CheckContainsDocument(documentId);
        }
    }
 
    private void CheckContainsAdditionalDocument(DocumentId documentId)
    {
        if (documentId == null)
        {
            throw new ArgumentNullException(nameof(documentId));
        }
 
        if (!ContainsAdditionalDocument(documentId))
        {
            throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
        }
    }
 
    private void CheckContainsAdditionalDocuments(ImmutableArray<DocumentId> documentIds)
    {
        if (documentIds.IsDefault)
        {
            throw new ArgumentNullException(nameof(documentIds));
        }
 
        foreach (var documentId in documentIds)
        {
            CheckContainsAdditionalDocument(documentId);
        }
    }
 
    private void CheckContainsAnalyzerConfigDocument(DocumentId documentId)
    {
        if (documentId == null)
        {
            throw new ArgumentNullException(nameof(documentId));
        }
 
        if (!ContainsAnalyzerConfigDocument(documentId))
        {
            throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
        }
    }
 
    private void CheckContainsAnalyzerConfigDocuments(ImmutableArray<DocumentId> documentIds)
    {
        if (documentIds.IsDefault)
        {
            throw new ArgumentNullException(nameof(documentIds));
        }
 
        foreach (var documentId in documentIds)
        {
            CheckContainsAnalyzerConfigDocument(documentId);
        }
    }
 
    /// <summary>
    /// Throws if setting the project references of project <paramref name="projectId"/> to specified <paramref name="projectReferences"/>
    /// would form a cycle in project dependency graph.
    /// </summary>
    private void CheckCircularProjectReferences(ProjectId projectId, IReadOnlyCollection<ProjectReference> projectReferences)
    {
        foreach (var projectReference in projectReferences)
        {
            if (projectId == projectReference.ProjectId)
            {
                throw new InvalidOperationException(WorkspacesResources.A_project_may_not_reference_itself);
            }
 
            if (this.SolutionState.ContainsTransitiveReference(projectReference.ProjectId, projectId))
            {
                throw new InvalidOperationException(
                    string.Format(WorkspacesResources.Adding_project_reference_from_0_to_1_will_cause_a_circular_reference,
                        projectId,
                        projectReference.ProjectId));
            }
        }
    }
 
    /// <summary>
    /// Throws if setting the project references of project <paramref name="projectId"/> to specified <paramref name="projectReferences"/>
    /// would form an invalid submission project chain.
    /// 
    /// Submission projects can reference at most one other submission project. Regular projects can't reference any.
    /// </summary>
    private void CheckSubmissionProjectReferences(ProjectId projectId, IEnumerable<ProjectReference> projectReferences, bool ignoreExistingReferences)
    {
        var projectState = this.SolutionState.GetRequiredProjectState(projectId);
 
        var isSubmission = projectState.IsSubmission;
        var hasSubmissionReference = !ignoreExistingReferences && projectState.ProjectReferences.Any(p => this.SolutionState.GetRequiredProjectState(p.ProjectId).IsSubmission);
 
        foreach (var projectReference in projectReferences)
        {
            // Note: need to handle reference to a project that's not included in the solution:
            var referencedProjectState = this.SolutionState.GetProjectState(projectReference.ProjectId);
            if (referencedProjectState != null && referencedProjectState.IsSubmission)
            {
                if (!isSubmission)
                {
                    throw new InvalidOperationException(WorkspacesResources.Only_submission_project_can_reference_submission_projects);
                }
 
                if (hasSubmissionReference)
                {
                    throw new InvalidOperationException(WorkspacesResources.This_submission_already_references_another_submission_project);
                }
 
                hasSubmissionReference = true;
            }
        }
    }
 
    internal SourceGeneratorExecutionVersion GetSourceGeneratorExecutionVersion(ProjectId projectId)
        => this.CompilationState.SourceGeneratorExecutionVersionMap[projectId];
}