Skip to content

Commit

Permalink
Merge pull request #73511 from CyrusNajmabadi/mapOrdering
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored May 16, 2024
2 parents f3dccaa + 9f693e9 commit 9f9f5a6
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 10 deletions.
14 changes: 11 additions & 3 deletions src/Workspaces/Core/Portable/Workspace/Solution/ProjectId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis;
#pragma warning restore CA1200 // Avoid using cref tags with a prefix
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
[DataContract]
public sealed class ProjectId : IEquatable<ProjectId>
public sealed class ProjectId : IEquatable<ProjectId>, IComparable<ProjectId>
{
/// <summary>
/// Checksum of this ProjectId, built only from <see cref="Id"/>.
Expand All @@ -37,8 +37,8 @@ public sealed class ProjectId : IEquatable<ProjectId>

/// <summary>
/// An optional name to show <em>only</em> for debugger-display purposes. This must not be used for any other
/// purpose. Importantly, it must not be part of the equality/hashing contract of this type (including <see
/// cref="_lazyChecksum"/>).
/// purpose. Importantly, it must not be part of the equality/hashing/comparable contract of this type (including
/// <see cref="_lazyChecksum"/>).
/// </summary>
[DataMember(Order = 1)]
private readonly string? _debugName;
Expand Down Expand Up @@ -114,4 +114,12 @@ internal Checksum Checksum
writer.WriteString(nameof(ProjectId));
writer.WriteGuid(@this.Id);
}), this);

int IComparable<ProjectId>.CompareTo(ProjectId? other)
{
if (other is null)
return 1;

return this.Id.CompareTo(other.Id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Collections;
Expand Down Expand Up @@ -48,14 +49,14 @@ public static SourceGeneratorExecutionVersion ReadFrom(ObjectReader reader)
/// 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(ImmutableSegmentedDictionary<ProjectId, SourceGeneratorExecutionVersion> map)
internal sealed class SourceGeneratorExecutionVersionMap(ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion> map)
{
public static readonly SourceGeneratorExecutionVersionMap Empty = new();

public ImmutableSegmentedDictionary<ProjectId, SourceGeneratorExecutionVersion> Map { get; } = map;
public ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion> Map { get; } = map;

public SourceGeneratorExecutionVersionMap()
: this(ImmutableSegmentedDictionary<ProjectId, SourceGeneratorExecutionVersion>.Empty)
: this(ImmutableSortedDictionary<ProjectId, SourceGeneratorExecutionVersion>.Empty)
{
}

Expand All @@ -75,6 +76,8 @@ public override bool Equals([NotNullWhen(true)] object? obj)

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)
{
Expand All @@ -86,7 +89,7 @@ public void WriteTo(ObjectWriter writer)
public static SourceGeneratorExecutionVersionMap Deserialize(ObjectReader reader)
{
var count = reader.ReadInt32();
var builder = ImmutableSegmentedDictionary.CreateBuilder<ProjectId, SourceGeneratorExecutionVersion>();
var builder = ImmutableSortedDictionary.CreateBuilder<ProjectId, SourceGeneratorExecutionVersion>();
for (var i = 0; i < count; i++)
{
var projectId = ProjectId.ReadFrom(reader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// 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.Frozen;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -69,7 +68,7 @@ static SourceGeneratorExecutionVersionMap GetUpdatedSourceGeneratorVersions(
// projects that transitively depend on that project, so that their generators will run as well when next
// asked.
var dependencyGraph = solution.GetProjectDependencyGraph();
var result = ImmutableSegmentedDictionary.CreateBuilder<ProjectId, SourceGeneratorExecutionVersion>();
var result = ImmutableSortedDictionary.CreateBuilder<ProjectId, SourceGeneratorExecutionVersion>();

// Determine if we want a major solution change, forcing regeneration of all projects.
var solutionMajor = projectIds.Any(t => t.projectId is null && t.forceRegeneration);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.Linq;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests;

public sealed class SourceGeneratorExecutionVersionMapTests
{
[Fact]
public void TestOrderingDoesNotMatter()
{
var projectId1 = ProjectId.CreateNewId();
var projectId2 = ProjectId.CreateNewId();
Assert.NotEqual(projectId1, projectId2);

var project1Kvp = new KeyValuePair<ProjectId, SourceGeneratorExecutionVersion>(projectId1, new(MajorVersion: 1, MinorVersion: 1));
var project2Kvp = new KeyValuePair<ProjectId, SourceGeneratorExecutionVersion>(projectId2, new(MajorVersion: 2, MinorVersion: 2));

var map1 = new SourceGeneratorExecutionVersionMap(ImmutableSortedDictionary.CreateRange([project1Kvp, project2Kvp]));
var map2 = new SourceGeneratorExecutionVersionMap(ImmutableSortedDictionary.CreateRange([project2Kvp, project1Kvp]));
Assert.True(map1.Map.SequenceEqual(map2.Map));

using var memoryStream1 = SerializableBytes.CreateWritableStream();
using var memoryStream2 = SerializableBytes.CreateWritableStream();
{
using var writer1 = new ObjectWriter(memoryStream1, leaveOpen: true);
{
map1.WriteTo(writer1);
}

using var writer2 = new ObjectWriter(memoryStream2, leaveOpen: true);
{
map2.WriteTo(writer2);
}

memoryStream1.Position = 0;
memoryStream2.Position = 0;

var array1 = memoryStream1.ToArray();
var array2 = memoryStream2.ToArray();

Assert.Equal(array1.Length, array2.Length);
Assert.True(array1.Length > 0);

Assert.True(array1.AsSpan().SequenceEqual(array2));

memoryStream1.Position = 0;
memoryStream2.Position = 0;

var rehydrated1 = SourceGeneratorExecutionVersionMap.Deserialize(ObjectReader.GetReader(memoryStream1, leaveOpen: true));
var rehydrated2 = SourceGeneratorExecutionVersionMap.Deserialize(ObjectReader.GetReader(memoryStream2, leaveOpen: true));

Assert.True(rehydrated1.Map.SequenceEqual(rehydrated2.Map));
Assert.True(rehydrated1.Map.SequenceEqual(map1.Map));
}
}
}

0 comments on commit 9f9f5a6

Please sign in to comment.