File: Workspace\Solution\SourceGeneratorExecutionVersion.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.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis;
 
/// <summary>
/// Represents the version of source generator execution that a project is at. Source generator results are kept around
/// as long as this version stays the same and we are in <see cref="SourceGeneratorExecutionPreference.Balanced"/>
/// mode. This has no effect when in <see cref="SourceGeneratorExecutionPreference.Automatic"/> mode (as we always rerun
/// generators on any change). This should effectively be used as a monotonically increasing value.
/// </summary>
/// <param name="MajorVersion">Controls the major version of source generation execution.  When this changes the
/// generator driver should be dropped and all generation should be rerun.</param>
/// <param name="MinorVersion">Controls the minor version of source generation execution.  When this changes the
/// generator driver can be reused and should incrementally determine what the new generated documents should be.
/// </param>
[DataContract]
internal readonly record struct SourceGeneratorExecutionVersion(
    [property: DataMember(Order = 0)] int MajorVersion,
    [property: DataMember(Order = 1)] int MinorVersion)
{
    public SourceGeneratorExecutionVersion IncrementMajorVersion()
        => new(MajorVersion + 1, MinorVersion: 0);
 
    public SourceGeneratorExecutionVersion IncrementMinorVersion()
        => new(MajorVersion, MinorVersion + 1);
 
    public void WriteTo(ObjectWriter writer)
    {
        writer.WriteInt32(MajorVersion);
        writer.WriteInt32(MinorVersion);
    }
 
    public static SourceGeneratorExecutionVersion ReadFrom(ObjectReader reader)
        => new(reader.ReadInt32(), reader.ReadInt32());
 
    public override string ToString()
        => $"{MajorVersion}.{MinorVersion}";
}
 
/// <summary>
/// Helper construct to allow a mapping from <see cref="ProjectId"/>s to <see cref="SourceGeneratorExecutionVersion"/>.
/// Limited to just the surface area the workspace needs.
/// </summary>
internal sealed class SourceGeneratorExecutionVersionMap(ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion> map)
{
    public static readonly SourceGeneratorExecutionVersionMap Empty = new();
 
    public ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion> Map { get; } = map;
 
    public SourceGeneratorExecutionVersionMap()
        : this(ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion>.Empty)
    {
    }
 
    public SourceGeneratorExecutionVersion this[ProjectId projectId] => Map[projectId];
 
    public static bool operator ==(SourceGeneratorExecutionVersionMap map1, SourceGeneratorExecutionVersionMap map2)
        => map1.Map == map2.Map;
 
    public static bool operator !=(SourceGeneratorExecutionVersionMap map1, SourceGeneratorExecutionVersionMap map2)
        => !(map1 == map2);
 
    public override int GetHashCode()
        => throw new InvalidOperationException();
 
    public override bool Equals([NotNullWhen(true)] object? obj)
        => obj is SourceGeneratorExecutionVersionMap map && this == map;
 
    public void WriteTo(ObjectWriter writer)
    {
        // Writing out the dictionary in order is fine.  That's because it's a sorted dictionary, and ProjectIds are
        // naturally comparable.
        writer.WriteInt32(Map.Count);
        foreach (var (projectId, version) in Map)
        {
            projectId.WriteTo(writer);
            version.WriteTo(writer);
        }
    }
 
    public static SourceGeneratorExecutionVersionMap Deserialize(ObjectReader reader)
    {
        var count = reader.ReadInt32();
        var builder = ImmutableSortedDictionary.CreateBuilder<ProjectId, SourceGeneratorExecutionVersion>();
        for (var i = 0; i < count; i++)
        {
            var projectId = ProjectId.ReadFrom(reader);
            var version = SourceGeneratorExecutionVersion.ReadFrom(reader);
            builder.Add(projectId, version);
        }
 
        return new(builder.ToImmutable());
    }
 
    public Checksum GetChecksum()
    {
        using var _ = ArrayBuilder<Checksum>.GetInstance(this.Map.Count * 2, out var checksums);
 
        foreach (var (projectId, version) in this.Map)
        {
            checksums.Add(projectId.Checksum);
            checksums.Add(Checksum.Create(version, static (v, w) => v.WriteTo(w)));
        }
 
        return Checksum.Create(checksums);
    }
 
    public override string ToString()
    {
        using var _ = PooledStringBuilder.GetInstance(out var builder);
 
        builder.AppendLine(nameof(SourceGeneratorExecutionVersionMap));
        foreach (var (projectId, version) in Map)
            builder.AppendLine($"    {projectId}: {version}");
 
        return builder.ToString();
    }
}