Skip to content

Commit

Permalink
chore: add ImmutableEquatableArray
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Jul 11, 2023
1 parent 136cd45 commit 9d5676b
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ SymbolAccessor symbolAccessor
);
}

public (MapperDescriptor descriptor, List<Diagnostic> errors) Build()
public (MapperDescriptor descriptor, List<Diagnostic> diagnostics) Build()
{
ReserveMethodNames();
ExtractObjectFactories();
Expand Down
12 changes: 12 additions & 0 deletions src/Riok.Mapperly/Helpers/HashHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Riok.Mapperly.Helpers;

internal class HashHelper
{
public static int Combine(int h1, int h2)
{

Check warning on line 6 in src/Riok.Mapperly/Helpers/HashHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Helpers/HashHelper.cs#L6

Added line #L6 was not covered by tests
// RyuJIT optimizes this to use the ROL instruction
// Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
return ((int)rol5 + h1) ^ h2;
}

Check warning on line 11 in src/Riok.Mapperly/Helpers/HashHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Helpers/HashHelper.cs#L9-L11

Added lines #L9 - L11 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly.Helpers;
Expand All @@ -19,7 +18,7 @@ public static IncrementalValuesProvider<TSource> WhereNotNull<TSource>(this Incr
/// <param name="diagnostics">The input <see cref="IncrementalValuesProvider{TValues}"/> sequence of diagnostics.</param>
public static void ReportDiagnostics(
this IncrementalGeneratorInitializationContext context,
IncrementalValueProvider<ImmutableArray<Diagnostic>> diagnostics
IncrementalValueProvider<ImmutableEquatableArray<Diagnostic>> diagnostics
)
{
context.RegisterSourceOutput(
Expand Down
82 changes: 82 additions & 0 deletions src/Riok.Mapperly/ImmutableEquatableArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using Riok.Mapperly.Helpers;

namespace Riok.Mapperly;

/// <summary>
/// Provides an immutable list implementation which implements sequence equality.
/// </summary>
public sealed class ImmutableEquatableArray<T> : IEquatable<ImmutableEquatableArray<T>>, IReadOnlyList<T>
where T : IEquatable<T>
{
public static ImmutableEquatableArray<T> Empty { get; } = new(Array.Empty<T>());

Check warning on line 15 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L15

Added line #L15 was not covered by tests

private readonly T[] _values;
public T this[int index] => _values[index];

Check warning on line 18 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L18

Added line #L18 was not covered by tests
public int Count => _values.Length;

public ImmutableEquatableArray(IEnumerable<T> values) => _values = values.ToArray();

public bool Equals(ImmutableEquatableArray<T>? other) => other != null && ((ReadOnlySpan<T>)_values).SequenceEqual(other._values);

public override bool Equals(object? obj) => obj is ImmutableEquatableArray<T> other && Equals(other);

public override int GetHashCode()
{
var hash = 0;

Check warning on line 29 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L28-L29

Added lines #L28 - L29 were not covered by tests
foreach (T value in _values)
{
hash = HashHelper.Combine(hash, value.GetHashCode());
}

Check warning on line 33 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L31-L33

Added lines #L31 - L33 were not covered by tests

return hash;
}

Check warning on line 36 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L35-L36

Added lines #L35 - L36 were not covered by tests

public Enumerator GetEnumerator() => new Enumerator(_values);

IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();

Check warning on line 42 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L42

Added line #L42 was not covered by tests

public struct Enumerator
{
private readonly T[] _values;
private int _index;

internal Enumerator(T[] values)
{
_values = values;
_index = -1;
}

public bool MoveNext()
{
var newIndex = _index + 1;

if ((uint)newIndex < (uint)_values.Length)
{
_index = newIndex;
return true;
}

return false;
}

public readonly T Current => _values[_index];
}
}

public static class ImmutableEquatableArray
{
public static ImmutableEquatableArray<T> Empty<T>()
where T : IEquatable<T> => ImmutableEquatableArray<T>.Empty;

Check warning on line 75 in src/Riok.Mapperly/ImmutableEquatableArray.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/ImmutableEquatableArray.cs#L75

Added line #L75 was not covered by tests

public static ImmutableEquatableArray<T> ToImmutableEquatableArray<T>(this IEnumerable<T> values)
where T : IEquatable<T> => new(values);

public static ImmutableEquatableArray<T> Create<T>(params T[] values)
where T : IEquatable<T> => values is { Length: > 0 } ? new(values) : ImmutableEquatableArray<T>.Empty;
}
12 changes: 7 additions & 5 deletions src/Riok.Mapperly/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Riok.Mapperly;
public class MapperGenerator : IIncrementalGenerator
{
private const string GeneratedFileSuffix = ".g.cs";
public const string AddMappersStep = "ImplementationSourceOutput";
public const string ReportDiagnosticsStep = "Diagnostics";

public static readonly string MapperAttributeName = typeof(MapperAttribute).FullName;

Expand All @@ -27,7 +29,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
);

// output the diagnostics
context.ReportDiagnostics(mappersWithDiagnostics.Select(static (source, _) => source.Diagnostics).WithTrackingName("diagnostics"));
context.ReportDiagnostics(
mappersWithDiagnostics.Select(static (source, _) => source.Diagnostics).WithTrackingName(ReportDiagnosticsStep)
);

// split into mapper name pairs
var mappers = mappersWithDiagnostics.SelectMany(static (x, _) => x.Mappers);
Expand Down Expand Up @@ -75,16 +79,14 @@ CancellationToken cancellationToken
if (!symbolAccessor.HasAttribute<MapperAttribute>(mapperSymbol))
continue;

var builder = new DescriptorBuilder(ctx, compilation, mapperSyntax, mapperSymbol, wellKnownTypes, symbolAccessor);
var descriptor = builder.Build();
var builder = new DescriptorBuilder(compilation, mapperSyntax, mapperSymbol, wellKnownTypes);
var builder = new DescriptorBuilder(compilation, mapperSyntax, mapperSymbol, wellKnownTypes, symbolAccessor);
var (descriptor, descriptorDiagnostics) = builder.Build();

diagnostics.AddRange(descriptorDiagnostics);
members.Add(new MapperNode(SourceEmitter.Build(descriptor), uniqueNameBuilder.New(descriptor.Name) + GeneratedFileSuffix));
}

cancellationToken.ThrowIfCancellationRequested();
return new MapperResults(members.ToImmutableArray(), diagnostics.ToImmutableArray());
return new MapperResults(members.ToImmutableEquatableArray(), diagnostics.ToImmutableEquatableArray());
}
}
48 changes: 3 additions & 45 deletions src/Riok.Mapperly/MapperResults.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,8 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly;

public readonly struct MapperResults : IEquatable<MapperResults>
public readonly record struct MapperResults(ImmutableEquatableArray<MapperNode> Mappers, ImmutableEquatableArray<Diagnostic> Diagnostics)
{
public static readonly MapperResults Empty = new(ImmutableArray<MapperNode>.Empty, ImmutableArray<Diagnostic>.Empty);

public MapperResults(ImmutableArray<MapperNode> mappers, ImmutableArray<Diagnostic> diagnostics)
{
Mappers = mappers;
Diagnostics = diagnostics;
}

public ImmutableArray<MapperNode> Mappers { get; }
public ImmutableArray<Diagnostic> Diagnostics { get; }

public bool Equals(MapperResults other)
{
return Mappers.SequenceEqual(other.Mappers) && Diagnostics.SequenceEqual(other.Diagnostics);
}

public override bool Equals(object? obj) => obj is MapperResults other && Equals(other);

public override int GetHashCode()
{
unchecked
{
var hash = 0;
foreach (var mapper in Mappers)
{
hash = Combine(hash, mapper.GetHashCode());
}
foreach (var diagnostic in Diagnostics)
{
hash = Combine(hash, diagnostic.GetHashCode());
}
return hash;
}

static int Combine(int h1, int h2)
{
// RyuJIT optimizes this to use the ROL instruction
// Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
return ((int)rol5 + h1) ^ h2;
}
}
public static readonly MapperResults Empty = new(ImmutableEquatableArray<MapperNode>.Empty, ImmutableEquatableArray<Diagnostic>.Empty);

Check warning on line 7 in src/Riok.Mapperly/MapperResults.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/MapperResults.cs#L7

Added line #L7 was not covered by tests
}
41 changes: 19 additions & 22 deletions test/Riok.Mapperly.Tests/Generator/IncrementalGeneratorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ namespace Riok.Mapperly.Tests.Generator;
[UsesVerify]
public class IncrementalGeneratorTest
{
private const string AddMappersStep = "ImplementationSourceOutput";
private const string ReportDiagnosticsStep = "diagnostics";

[Fact]
public void AddingUnrelatedTypeDoesNotRegenerateOriginal()
{
Expand All @@ -23,8 +20,8 @@ public void AddingUnrelatedTypeDoesNotRegenerateOriginal()
var compilation2 = compilation1.AddSyntaxTrees(TestSourceBuilder.SyntaxTree("struct MyValue {}"));
var driver2 = driver1.RunGenerators(compilation2);

AssertRunResults(AddMappersStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(MapperGenerator.AddMappersStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(MapperGenerator.ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Cached);
}

[Fact]
Expand All @@ -38,17 +35,17 @@ public void AddingNewMapperDoesNotRegenerateOriginal()

var secondary = TestSourceBuilder.SyntaxTree(
"""
using Riok.Mapperly.Abstractions;

namespace Test.B
{
[Mapper]
internal partial class BarFooMapper
{
internal partial string BarToFoo(string value);
}
}
"""
using Riok.Mapperly.Abstractions;

namespace Test.B
{
[Mapper]
internal partial class BarFooMapper
{
internal partial string BarToFoo(string value);
}
}
"""
);

var syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default);
Expand All @@ -59,8 +56,8 @@ internal partial class BarFooMapper
var compilation2 = compilation1.AddSyntaxTrees(secondary);
var driver2 = driver1.RunGenerators(compilation2);

AssertRunResults(AddMappersStep, driver2, IncrementalStepRunReason.Cached, IncrementalStepRunReason.New);
AssertRunResults(ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Modified);
AssertRunResults(MapperGenerator.AddMappersStep, driver2, IncrementalStepRunReason.Cached, IncrementalStepRunReason.New);
AssertRunResults(MapperGenerator.ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Modified);
}

[Fact]
Expand All @@ -80,8 +77,8 @@ public void AppendingUnrelatedTypeDoesNotRegenerateOriginal()
var compilation2 = compilation1.ReplaceSyntaxTree(compilation1.SyntaxTrees.First(), newTree);
var driver2 = driver1.RunGenerators(compilation2);

AssertRunResults(AddMappersStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(MapperGenerator.AddMappersStep, driver2, IncrementalStepRunReason.Cached);
AssertRunResults(MapperGenerator.ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Cached);
}

[Fact]
Expand All @@ -107,8 +104,8 @@ public void ModifyingMapperDoesRegenerateOriginal()
var compilation2 = compilation1.ReplaceSyntaxTree(compilation1.SyntaxTrees.First(), newTree);
var driver2 = driver1.RunGenerators(compilation2);

AssertRunResults(AddMappersStep, driver2, IncrementalStepRunReason.New);
AssertRunResults(ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Modified);
AssertRunResults(MapperGenerator.AddMappersStep, driver2, IncrementalStepRunReason.New);
AssertRunResults(MapperGenerator.ReportDiagnosticsStep, driver2, IncrementalStepRunReason.Modified);
}

private static void AssertRunResults(string name, GeneratorDriver driver, params IncrementalStepRunReason[] runReasons)
Expand Down

0 comments on commit 9d5676b

Please sign in to comment.