diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacityMemberSetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacityMemberSetter.cs deleted file mode 100644 index 1561c00cc4..0000000000 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacityMemberSetter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Riok.Mapperly.Symbols.Members; - -namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; - -internal class CapacityMemberSetter(IMappableMember targetCapacityMember, IMemberSetter setter) : ICapacityMemberSetter -{ - public bool SupportsCoalesceAssignment => setter.SupportsCoalesceAssignment; - - public IMappableMember TargetCapacity => targetCapacityMember; - - public ExpressionSyntax BuildAssignment( - ExpressionSyntax? baseAccess, - ExpressionSyntax valueToAssign, - bool coalesceAssignment = false - ) => setter.BuildAssignment(baseAccess, valueToAssign, coalesceAssignment); - - public static ICapacityMemberSetter Build(MappingBuilderContext ctx, IMappableMember member) => - new CapacityMemberSetter(member, member.BuildSetter(ctx.UnsafeAccessorContext)); -} diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacitySetterBuilder.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacitySetterBuilder.cs index b79e95df62..a30d963c60 100644 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacitySetterBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/CapacitySetterBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Riok.Mapperly.Symbols.Members; namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; @@ -48,7 +49,7 @@ bool includeTargetCount return new NonEnumeratedCapacitySetter(capacitySetter, targetCount, nonEnumeratedCountMethod); } - private static ICapacityMemberSetter? BuildCapacitySetter(MappingBuilderContext ctx, CollectionInfo target) + private static IMemberSetter? BuildCapacitySetter(MappingBuilderContext ctx, CollectionInfo target) { var ensureCapacityMethod = ctx .SymbolAccessor.GetAllMethods(target.Type, EnsureCapacityMethodSetter.EnsureCapacityMethodName) @@ -58,7 +59,7 @@ bool includeTargetCount var member = ctx.SymbolAccessor.GetMappableMember(target.Type, CapacityMemberName); if (member is { CanSetDirectly: true, IsInitOnly: false, Type.SpecialType: SpecialType.System_Int32 }) - return CapacityMemberSetter.Build(ctx, member); + return member.BuildSetter(ctx.UnsafeAccessorContext); return null; } diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/EnsureCapacityMethodSetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/EnsureCapacityMethodSetter.cs index b5b9fdd5dd..1214487e7e 100644 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/EnsureCapacityMethodSetter.cs +++ b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/EnsureCapacityMethodSetter.cs @@ -7,7 +7,7 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; /// /// Ensures the capacity of a collection by calling `EnsureCapacity(int)` /// -internal class EnsureCapacityMethodSetter : ICapacityMemberSetter +internal class EnsureCapacityMethodSetter : IMemberSetter { public static readonly EnsureCapacityMethodSetter Instance = new(); @@ -17,8 +17,6 @@ private EnsureCapacityMethodSetter() { } public bool SupportsCoalesceAssignment => false; - public IMappableMember? TargetCapacity => null; - public ExpressionSyntax BuildAssignment(ExpressionSyntax? baseAccess, ExpressionSyntax valueToAssign, bool coalesceAssignment = false) { if (baseAccess == null) diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacityMemberSetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacityMemberSetter.cs deleted file mode 100644 index ebc19023ec..0000000000 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacityMemberSetter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Riok.Mapperly.Symbols.Members; - -namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; - -/// -/// Sets the capacity of a collection to the provided count. -/// -public interface ICapacityMemberSetter : IMemberSetter -{ - IMappableMember? TargetCapacity { get; } -} diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacitySetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacitySetter.cs index 9b3444ceee..923d2b2465 100644 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacitySetter.cs +++ b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/ICapacitySetter.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Descriptors.Mappings; -using Riok.Mapperly.Symbols.Members; namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; @@ -9,7 +8,5 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; /// public interface ICapacitySetter { - IMappableMember? CapacityTargetMember { get; } - StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target); } diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/NonEnumeratedCapacitySetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/NonEnumeratedCapacitySetter.cs index dc83e0a897..2c136c7ee8 100644 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/NonEnumeratedCapacitySetter.cs +++ b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/NonEnumeratedCapacitySetter.cs @@ -18,16 +18,11 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; /// target.EnsureCapacity(sourceCount + target.Count); /// /// -public class NonEnumeratedCapacitySetter( - ICapacityMemberSetter capacitySetter, - IMemberGetter? targetAccessor, - IMethodSymbol getNonEnumeratedMethod -) : ICapacitySetter +public class NonEnumeratedCapacitySetter(IMemberSetter capacitySetter, IMemberGetter? targetAccessor, IMethodSymbol getNonEnumeratedMethod) + : ICapacitySetter { private const string SourceCountVariableName = "sourceCount"; - public IMappableMember? CapacityTargetMember => capacitySetter.TargetCapacity; - public StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target) { var sourceCountName = ctx.NameBuilder.New(SourceCountVariableName); diff --git a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/SimpleCapacitySetter.cs b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/SimpleCapacitySetter.cs index b536b9c1eb..18b5bd68e7 100644 --- a/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/SimpleCapacitySetter.cs +++ b/src/Riok.Mapperly/Descriptors/Enumerables/Capacity/SimpleCapacitySetter.cs @@ -15,11 +15,9 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity; /// target.Capacity = source.Length + target.Count; /// /// -public class SimpleCapacitySetter(ICapacityMemberSetter capacitySetter, IMemberGetter? targetAccessor, IMemberGetter sourceAccessor) +public class SimpleCapacitySetter(IMemberSetter capacitySetter, IMemberGetter? targetAccessor, IMemberGetter sourceAccessor) : ICapacitySetter { - public IMappableMember? CapacityTargetMember => capacitySetter.TargetCapacity; - public StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target) { var count = sourceAccessor.BuildAccess(ctx.Source); diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs index 2a10b78fd2..e3695e689e 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs @@ -16,6 +16,7 @@ public interface IMembersBuilderContext MappingBuilderContext BuilderContext { get; } void IgnoreMembers(IMappableMember member); + void IgnoreMembers(string memberName); void SetMembersMapped(MemberMappingInfo members); diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs index bc84bdc6e2..1d650e6f73 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs @@ -45,6 +45,8 @@ protected void SetTargetMemberMapped(string targetMemberName, bool ignoreCase = public void IgnoreMembers(IMappableMember member) => _state.IgnoreMembers(member); + public void IgnoreMembers(string memberName) => _state.IgnoreMembers(memberName); + public void ConsumeMemberConfigs(MemberMappingInfo members) { if (members.Configuration != null) diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingState.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingState.cs index f1e54a3526..32c9ba9f6b 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingState.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingState.cs @@ -91,15 +91,17 @@ public void MappingAdded(MemberMappingInfo info, bool ignoreTargetCasing) SetMembersMapped(info, ignoreTargetCasing); } - public void IgnoreMembers(IMappableMember member) + public void IgnoreMembers(IMappableMember member) => IgnoreMembers(member.Name); + + public void IgnoreMembers(string name) { - _unmappedSourceMemberNames.Remove(member.Name); - _unmappedTargetMemberNames.Remove(member.Name); - ignoredSourceMemberNames.Add(member.Name); + _unmappedSourceMemberNames.Remove(name); + _unmappedTargetMemberNames.Remove(name); + ignoredSourceMemberNames.Add(name); - if (!HasMemberConfig(member.Name)) + if (!HasMemberConfig(name)) { - targetMembers.Remove(member.Name); + targetMembers.Remove(name); } } diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/EnumerableMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/EnumerableMappingBodyBuilder.cs index 55e692786f..a39555aab2 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/EnumerableMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/EnumerableMappingBodyBuilder.cs @@ -9,6 +9,7 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; internal static class EnumerableMappingBodyBuilder { private const string SystemNamespaceName = "System"; + private const string CapacityMemberName = "Capacity"; private static readonly IReadOnlyCollection _sourceCountAlias = [ @@ -35,13 +36,13 @@ public static void BuildMappingBody(MappingBuilderContext ctx, IEnumerableMappin // include the target count as the target could already include elements if (CapacitySetterBuilder.TryBuildCapacitySetter(ctx, mapping.CollectionInfos, true) is { } capacitySetter) { - if (capacitySetter.CapacityTargetMember != null) - { - mappingCtx.IgnoreMembers(capacitySetter.CapacityTargetMember); - } - + mappingCtx.IgnoreMembers(CapacityMemberName); mapping.AddCapacitySetter(capacitySetter); } + else + { + IgnoreCapacityIfSystemType(mappingCtx); + } ObjectMemberMappingBodyBuilder.BuildMappingBody(mappingCtx); mappingCtx.AddDiagnostics(); @@ -72,6 +73,15 @@ private static void IgnoreSystemMembers(IMembersBuilderContext ctx, ITypeS } } + private static void IgnoreCapacityIfSystemType(IMembersBuilderContext ctx) + where T : IEnumerableMapping + { + if (ctx.Mapping.SourceType.IsInRootNamespace(SystemNamespaceName) || ctx.Mapping.TargetType.IsInRootNamespace(SystemNamespaceName)) + { + ctx.IgnoreMembers(CapacityMemberName); + } + } + private static void BuildConstructorMapping(INewInstanceBuilderContext ctx) { // allow source count being mapped to a target constructor parameter @@ -108,12 +118,12 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext WalkTypeHierarchy(this ITypeSymbol symb internal static bool IsInRootNamespace(this ISymbol symbol, string ns) { var namespaceSymbol = symbol.ContainingNamespace; - while (namespaceSymbol.ContainingNamespace is { IsGlobalNamespace: false }) + while (namespaceSymbol?.ContainingNamespace is { IsGlobalNamespace: false }) { namespaceSymbol = namespaceSymbol.ContainingNamespace; } - return string.Equals(namespaceSymbol.Name, ns, StringComparison.Ordinal); + return namespaceSymbol != null && string.Equals(namespaceSymbol.Name, ns, StringComparison.Ordinal); } } diff --git a/test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs b/test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs index 72d8573655..8e1b71f2f6 100644 --- a/test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs @@ -381,4 +381,57 @@ public void CustomCollectionToCustomCollectionWithObjectFactory() """ ); } + + [Fact] + public void CustomCollectionToGetOnlyICollection() + { + var source = TestSourceBuilder.Mapping( + "A", + "B", + "class A { public C Value { get; } }", + "class B { public ICollection Value { get; } }", + "class C : IEnumerable { public int Capacity { get; } public int Count { get; } }" + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowAndIncludeAllDiagnostics) + .Should() + .HaveAssertedAllDiagnostics() + .HaveSingleMethodBody( + """ + var target = new global::B(); + foreach (var item in source.Value) + { + target.Value.Add(item); + } + return target; + """ + ); + } + + [Fact] + public void CustomCollectionToGetOnlyList() + { + var source = TestSourceBuilder.Mapping( + "A", + "B", + "class A { public C Value { get; } }", + "class B { public List Value { get; } }", + "class C : IEnumerable { public int Capacity { get; } public int Count { get; } }" + ); + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowAndIncludeAllDiagnostics) + .Should() + .HaveAssertedAllDiagnostics() + .HaveSingleMethodBody( + """ + var target = new global::B(); + target.Value.EnsureCapacity(source.Value.Count + target.Value.Count); + foreach (var item in source.Value) + { + target.Value.Add(item); + } + return target; + """ + ); + } }