Skip to content

Commit

Permalink
fix: use ToList/ToArray to clone enumerable interfaces (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed May 17, 2023
1 parent 082d877 commit f947cbf
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 7 deletions.
Expand Up @@ -55,7 +55,7 @@ public static class EnumerableMappingBuilder

// try linq mapping: x.Select(Map).ToArray/ToList
// if that doesn't work do a foreach with add calls
var (canMapWithLinq, collectMethodName) = ResolveCollectMethodName(ctx);
var (canMapWithLinq, collectMethodName) = ResolveCollectMethodName(ctx, elementMapping.IsSynthetic);
if (canMapWithLinq)
return BuildLinqMapping(ctx, elementMapping, collectMethodName);

Expand Down Expand Up @@ -182,17 +182,22 @@ private static LinqConstructorMapping BuildLinqConstructorMapping(MappingBuilder
return null;
}

private static (bool CanMapWithLinq, string? CollectMethod) ResolveCollectMethodName(MappingBuilderContext ctx)
private static (bool CanMapWithLinq, string? CollectMethod) ResolveCollectMethodName(
MappingBuilderContext ctx,
bool elementMappingIsSynthetic
)
{
// if the target is an array we need to collect to array
if (ctx.Target.IsArrayType())
return (true, ToArrayMethodName);

// if the target is an IEnumerable<T> don't collect at all.
if (SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.IEnumerableT))
// if the target is an IEnumerable<T> don't collect at all
// except deep cloning is enabled.
var targetIsIEnumerable = SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.IEnumerableT);
if (targetIsIEnumerable && !ctx.MapperConfiguration.UseDeepCloning)
return (true, null);

// if the target is IReadOnlyCollection<T>
// if the target is IReadOnlyCollection<T> or IEnumerable<T>
// and the count of the source is known (array, IReadOnlyCollection<T>, ICollection<T>) we collect to array
// for performance/space reasons
var targetIsReadOnlyCollection = SymbolEqualityComparer.Default.Equals(
Expand All @@ -203,12 +208,13 @@ private static (bool CanMapWithLinq, string? CollectMethod) ResolveCollectMethod
ctx.Source.IsArrayType()
|| ctx.Source.ImplementsGeneric(ctx.Types.IReadOnlyCollectionT, out _)
|| ctx.Source.ImplementsGeneric(ctx.Types.ICollectionT, out _);
if (targetIsReadOnlyCollection && sourceCountIsKnown)
if ((targetIsReadOnlyCollection || targetIsIEnumerable) && sourceCountIsKnown)
return (true, ToArrayMethodName);

// if target is a IReadOnlyCollection<T>, IList<T>, List<T> or ICollection<T> with ToList()
// if target is a IReadOnlyCollection<T>, IEnumerable<T>, IList<T>, List<T> or ICollection<T> with ToList()
return
targetIsReadOnlyCollection
|| targetIsIEnumerable
|| SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.IReadOnlyListT)
|| SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.IListT)
|| SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.ListT)
Expand Down
21 changes: 21 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumerableDeepCloningTest.cs
Expand Up @@ -194,4 +194,25 @@ public void ArrayToArrayOfMutableStructDeepCloningTargetNameTaken()
"""
);
}

[Fact]
public void EnumerableOfPrimitivesToEnumerableDeepCloning()
{
var source = TestSourceBuilder.Mapping("IEnumerable<int>", "IEnumerable<int>", TestSourceBuilderOptions.WithDeepCloning);
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return global::System.Linq.Enumerable.ToList(source);");
}

[Fact]
public void ReadOnlyCollectionOfPrimitivesToEnumerableDeepCloning()
{
var source = TestSourceBuilder.Mapping("IReadOnlyCollection<int>", "IEnumerable<int>", TestSourceBuilderOptions.WithDeepCloning);
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return global::System.Linq.Enumerable.ToArray(source);");
}

[Fact]
public void EnumerableOfPrimitivesToReadOnlyCollectionDeepCloning()
{
var source = TestSourceBuilder.Mapping("IEnumerable<int>", "IReadOnlyCollection<int>", TestSourceBuilderOptions.WithDeepCloning);
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return global::System.Linq.Enumerable.ToList(source);");
}
}

0 comments on commit f947cbf

Please sign in to comment.