Skip to content

Commit

Permalink
feat: add queue and stack mapping (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison authored Mar 28, 2023
1 parent 0cf85b3 commit 5040bf5
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,16 @@ public static class EnumerableMappingBuilder
if (BuildElementMapping(ctx) is not { } elementMapping)
return null;

if (!ctx.Target.ImplementsGeneric(ctx.Types.ICollection, out _))
return null;
if (ctx.Target.ImplementsGeneric(ctx.Types.Stack, out _))
return new ForEachAddEnumerableExistingTargetMapping(ctx.Source, ctx.Target, elementMapping, nameof(Stack<object>.Push));

if (ctx.Target.ImplementsGeneric(ctx.Types.Queue, out _))
return new ForEachAddEnumerableExistingTargetMapping(ctx.Source, ctx.Target, elementMapping, nameof(Queue<object>.Enqueue));

return new ForEachAddEnumerableExistingTargetMapping(
ctx.Source,
ctx.Target,
elementMapping);
if (ctx.Target.ImplementsGeneric(ctx.Types.ICollection, out _))
return new ForEachAddEnumerableExistingTargetMapping(ctx.Source, ctx.Target, elementMapping, nameof(ICollection<object>.Add));

return null;
}

private static ITypeMapping? BuildElementMapping(MappingBuilderContext ctx)
Expand Down Expand Up @@ -96,7 +99,7 @@ private static LinqEnumerableMapping BuildLinqMapping(
return new LinqEnumerableMapping(ctx.Source, ctx.Target, elementMapping, selectMethod, collectMethod);
}

private static ForEachAddEnumerableMapping? BuildCustomTypeMapping(
private static ExistingTargetMappingMethodWrapper? BuildCustomTypeMapping(
MappingBuilderContext ctx,
ITypeMapping elementMapping)
{
Expand All @@ -106,9 +109,16 @@ private static LinqEnumerableMapping BuildLinqMapping(
return null;
}

return ctx.Target.ImplementsGeneric(ctx.Types.ICollection, out _)
? new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory)
: null;
if (ctx.Target.ImplementsGeneric(ctx.Types.Stack, out _))
return new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory, nameof(Stack<object>.Push));

if (ctx.Target.ImplementsGeneric(ctx.Types.Queue, out _))
return new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory, nameof(Queue<object>.Enqueue));

if (ctx.Target.ImplementsGeneric(ctx.Types.ICollection, out _))
return new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory, nameof(ICollection<object>.Add));

return null;
}

private static (bool CanMapWithLinq, string? CollectMethod) ResolveCollectMethodName(MappingBuilderContext ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,26 @@ namespace Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
public class ForEachAddEnumerableExistingTargetMapping : ExistingTargetMapping
{
private const string LoopItemVariableName = "item";
private const string AddMethodName = nameof(ICollection<object>.Add);

private readonly ITypeMapping _elementMapping;
private readonly string _insertMethodName;

public ForEachAddEnumerableExistingTargetMapping(
ITypeSymbol sourceType,
ITypeSymbol targetType,
ITypeMapping elementMapping)
ITypeMapping elementMapping,
string insertMethodName)
: base(sourceType, targetType)
{
_elementMapping = elementMapping;
_insertMethodName = insertMethodName;
}

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
var loopItemVariableName = ctx.NameBuilder.New(LoopItemVariableName);
var convertedSourceItemExpression = _elementMapping.Build(ctx.WithSource(loopItemVariableName));
var addMethod = MemberAccess(target, AddMethodName);
var addMethod = MemberAccess(target, _insertMethodName);

return new StatementSyntax[]
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ public ForEachAddEnumerableMapping(
ITypeSymbol sourceType,
ITypeSymbol targetType,
ITypeMapping elementMapping,
ObjectFactory? objectFactory)
: base(new ForEachAddEnumerableExistingTargetMapping(sourceType, targetType, elementMapping))
ObjectFactory? objectFactory,
string insertMethodName)
: base(new ForEachAddEnumerableExistingTargetMapping(sourceType, targetType, elementMapping, insertMethodName))
{
_objectFactory = objectFactory;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Riok.Mapperly/Descriptors/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class WellKnownTypes
private INamedTypeSymbol? _iReadOnlyCollection;
private INamedTypeSymbol? _iList;
private INamedTypeSymbol? _list;
private INamedTypeSymbol? _stack;
private INamedTypeSymbol? _queue;
private INamedTypeSymbol? _iReadOnlyList;
private INamedTypeSymbol? _keyValuePair;
private INamedTypeSymbol? _dictionary;
Expand Down Expand Up @@ -55,6 +57,8 @@ internal WellKnownTypes(Compilation compilation)
public INamedTypeSymbol IReadOnlyCollection => _iReadOnlyCollection ??= GetTypeSymbol(typeof(IReadOnlyCollection<>));
public INamedTypeSymbol IList => _iList ??= GetTypeSymbol(typeof(IList<>));
public INamedTypeSymbol List => _list ??= GetTypeSymbol(typeof(List<>));
public INamedTypeSymbol Stack => _stack ??= GetTypeSymbol(typeof(Stack<>));
public INamedTypeSymbol Queue => _queue ??= GetTypeSymbol(typeof(Queue<>));
public INamedTypeSymbol IReadOnlyList => _iReadOnlyList ??= GetTypeSymbol(typeof(IReadOnlyList<>));
public INamedTypeSymbol KeyValuePair => _keyValuePair ??= GetTypeSymbol(typeof(KeyValuePair<,>));
public INamedTypeSymbol Dictionary => _dictionary ??= GetTypeSymbol(typeof(Dictionary<,>));
Expand Down
110 changes: 110 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,94 @@ public void EnumerableToCustomCollection()
""");
}

[Fact]
public void EnumerableToStack()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public IEnumerable<int> Value { get; } }",
"class B { public Stack<long> Value { get; } }");
TestHelper.GenerateMapper(source)
.Should()
.HaveMapMethodBody(
"""
var target = new B();
foreach (var item in source.Value)
{
target.Value.Push((long)item);
}
return target;
""");
}

[Fact]
public void EnumerableToQueue()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public IEnumerable<int> Value { get; } }",
"class B { public Queue<long> Value { get; } }");
TestHelper.GenerateMapper(source)
.Should()
.HaveMapMethodBody(
"""
var target = new B();
foreach (var item in source.Value)
{
target.Value.Enqueue((long)item);
}
return target;
""");
}

[Fact]
public void EnumerableToCreatedStack()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public IEnumerable<int> Value { get; } }",
"class B { public Stack<long> Value { get; set; } }");
TestHelper.GenerateMapper(source)
.Should()
.HaveMethodBody("MapToStack",
"""
var target = new System.Collections.Generic.Stack<long>();
foreach (var item in source)
{
target.Push((long)item);
}
return target;
""");
}

[Fact]
public void EnumerableToCreatedQueue()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public IEnumerable<int> Value { get; } }",
"class B { public Queue<long> Value { get; set; } }");
TestHelper.GenerateMapper(source)
.Should()
.HaveMethodBody("MapToQueue",
"""
var target = new System.Collections.Generic.Queue<long>();
foreach (var item in source)
{
target.Enqueue((long)item);
}
return target;
""");
}

[Fact]
public void EnumerableToCustomCollectionWithObjectFactory()
{
Expand Down Expand Up @@ -523,6 +611,28 @@ public Task MapToExistingCollectionShouldWork()
return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task MapToExistingStackShouldWork()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"partial void Map(List<A>? source, Stack<B> target);",
"class A { public string Value { get; set; } }",
"class B { public string Value { get; set; } }");

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task MapToExistingQueueShouldWork()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"partial void Map(List<A>? source, Queue<B> target);",
"class A { public string Value { get; set; } }",
"class B { public string Value { get; set; } }");

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task MapToReadOnlyNullableCollectionProperty()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//HintName: Mapper.g.cs
#nullable enable
public partial class Mapper
{
private partial void Map(System.Collections.Generic.List<A>? source, System.Collections.Generic.Queue<B> target)
{
if (source == null)
return;
foreach (var item in source)
{
target.Enqueue(MapToB(item));
}
}

private B MapToB(A source)
{
var target = new B();
target.Value = source.Value;
return target;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//HintName: Mapper.g.cs
#nullable enable
public partial class Mapper
{
private partial void Map(System.Collections.Generic.List<A>? source, System.Collections.Generic.Stack<B> target)
{
if (source == null)
return;
foreach (var item in source)
{
target.Push(MapToB(item));
}
}

private B MapToB(A source)
{
var target = new B();
target.Value = source.Value;
return target;
}
}

0 comments on commit 5040bf5

Please sign in to comment.