From 5040bf516ba636292c4269500ff52c7f3254ca13 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:59:06 +0100 Subject: [PATCH] feat: add queue and stack mapping (#297) --- .../EnumerableMappingBuilder.cs | 30 +++-- ...rEachAddEnumerableExistingTargetMapping.cs | 8 +- .../Mappings/ForEachAddEnumerableMapping.cs | 5 +- .../Descriptors/WellKnownTypes.cs | 4 + .../Mapping/EnumerableTest.cs | 110 ++++++++++++++++++ ...istingQueueShouldWork#Mapper.g.verified.cs | 21 ++++ ...istingStackShouldWork#Mapper.g.verified.cs | 21 ++++ 7 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingQueueShouldWork#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingStackShouldWork#Mapper.g.verified.cs diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs index 1dd53de37b..3ecec5b68c 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs @@ -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.Push)); + + if (ctx.Target.ImplementsGeneric(ctx.Types.Queue, out _)) + return new ForEachAddEnumerableExistingTargetMapping(ctx.Source, ctx.Target, elementMapping, nameof(Queue.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.Add)); + + return null; } private static ITypeMapping? BuildElementMapping(MappingBuilderContext ctx) @@ -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) { @@ -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.Push)); + + if (ctx.Target.ImplementsGeneric(ctx.Types.Queue, out _)) + return new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory, nameof(Queue.Enqueue)); + + if (ctx.Target.ImplementsGeneric(ctx.Types.ICollection, out _)) + return new ForEachAddEnumerableMapping(ctx.Source, ctx.Target, elementMapping, objectFactory, nameof(ICollection.Add)); + + return null; } private static (bool CanMapWithLinq, string? CollectMethod) ResolveCollectMethodName(MappingBuilderContext ctx) diff --git a/src/Riok.Mapperly/Descriptors/Mappings/ExistingTarget/ForEachAddEnumerableExistingTargetMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/ExistingTarget/ForEachAddEnumerableExistingTargetMapping.cs index 039cab45a1..e73dcfb17e 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/ExistingTarget/ForEachAddEnumerableExistingTargetMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/ExistingTarget/ForEachAddEnumerableExistingTargetMapping.cs @@ -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.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 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[] { diff --git a/src/Riok.Mapperly/Descriptors/Mappings/ForEachAddEnumerableMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/ForEachAddEnumerableMapping.cs index d65cce3e42..d2e08bec68 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/ForEachAddEnumerableMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/ForEachAddEnumerableMapping.cs @@ -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; } diff --git a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs index 4a1afaf13f..90033efda5 100644 --- a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs +++ b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs @@ -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; @@ -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<,>)); diff --git a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs index fb804586a6..2e2655c2bd 100644 --- a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs @@ -476,6 +476,94 @@ public void EnumerableToCustomCollection() """); } + [Fact] + public void EnumerableToStack() + { + var source = TestSourceBuilder.Mapping( + "A", + "B", + "class A { public IEnumerable Value { get; } }", + "class B { public Stack 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 Value { get; } }", + "class B { public Queue 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 Value { get; } }", + "class B { public Stack Value { get; set; } }"); + TestHelper.GenerateMapper(source) + .Should() + .HaveMethodBody("MapToStack", + """ + var target = new System.Collections.Generic.Stack(); + 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 Value { get; } }", + "class B { public Queue Value { get; set; } }"); + TestHelper.GenerateMapper(source) + .Should() + .HaveMethodBody("MapToQueue", + """ + var target = new System.Collections.Generic.Queue(); + foreach (var item in source) + { + target.Enqueue((long)item); + } + + return target; + """); + } + [Fact] public void EnumerableToCustomCollectionWithObjectFactory() { @@ -523,6 +611,28 @@ public Task MapToExistingCollectionShouldWork() return TestHelper.VerifyGenerator(source); } + [Fact] + public Task MapToExistingStackShouldWork() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + "partial void Map(List? source, Stack 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? source, Queue target);", + "class A { public string Value { get; set; } }", + "class B { public string Value { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + [Fact] public Task MapToReadOnlyNullableCollectionProperty() { diff --git a/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingQueueShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingQueueShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..21d4ea9240 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingQueueShouldWork#Mapper.g.verified.cs @@ -0,0 +1,21 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial void Map(System.Collections.Generic.List? source, System.Collections.Generic.Queue 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; + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingStackShouldWork#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingStackShouldWork#Mapper.g.verified.cs new file mode 100644 index 0000000000..9e2ff93665 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/EnumerableTest.MapToExistingStackShouldWork#Mapper.g.verified.cs @@ -0,0 +1,21 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial void Map(System.Collections.Generic.List? source, System.Collections.Generic.Stack 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; + } +} \ No newline at end of file