Skip to content

Commit

Permalink
ignore capacity related diagnostics for system collection types when …
Browse files Browse the repository at this point in the history
…mapping from custom type
  • Loading branch information
latonz committed Oct 14, 2024
1 parent a667d41 commit 9ed9d0e
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 67 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;
/// <summary>
/// Ensures the capacity of a collection by calling `EnsureCapacity(int)`
/// </summary>
internal class EnsureCapacityMethodSetter : ICapacityMemberSetter
internal class EnsureCapacityMethodSetter : IMemberSetter
{
public static readonly EnsureCapacityMethodSetter Instance = new();

Expand All @@ -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)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -9,7 +8,5 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;
/// </summary>
public interface ICapacitySetter
{
IMappableMember? CapacityTargetMember { get; }

StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;
/// target.EnsureCapacity(sourceCount + target.Count);
/// </code>
/// </remarks>
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;
/// target.Capacity = source.Length + target.Count;
/// </code>
/// </remarks>
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface IMembersBuilderContext<out T>
MappingBuilderContext BuilderContext { get; }

void IgnoreMembers(IMappableMember member);
void IgnoreMembers(string memberName);

void SetMembersMapped(MemberMappingInfo members);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
using Riok.Mapperly.Descriptors.Enumerables.Capacity;
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;

Check failure on line 5 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/EnumerableMappingBodyBuilder.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Using directive is unnecessary.
using Riok.Mapperly.Helpers;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders;

internal static class EnumerableMappingBodyBuilder
{
private const string SystemNamespaceName = "System";
private const string CapacityMemberName = "Capacity";

private static readonly IReadOnlyCollection<string> _sourceCountAlias =
[
Expand All @@ -35,13 +37,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();
Expand Down Expand Up @@ -72,6 +74,15 @@ private static void IgnoreSystemMembers<T>(IMembersBuilderContext<T> ctx, ITypeS
}
}

private static void IgnoreCapacityIfSystemType<T>(IMembersBuilderContext<T> ctx)
where T : IEnumerableMapping
{
if (ctx.Mapping.SourceType.IsInRootNamespace(SystemNamespaceName) || ctx.Mapping.TargetType.IsInRootNamespace(SystemNamespaceName))
{
ctx.IgnoreMembers(CapacityMemberName);
}
}

private static void BuildConstructorMapping(INewInstanceBuilderContext<INewInstanceEnumerableMapping> ctx)
{
// allow source count being mapped to a target constructor parameter
Expand Down Expand Up @@ -108,12 +119,12 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext<INewInsta
ctx.IgnoreMembers(ctx.Mapping.CollectionInfos.Source.CountMember);
}

if (capacitySetter.CapacityTargetMember != null)
{
ctx.IgnoreMembers(capacitySetter.CapacityTargetMember);
}

ctx.IgnoreMembers(CapacityMemberName);
ctx.Mapping.AddCapacitySetter(capacitySetter);
}
else
{
IgnoreCapacityIfSystemType(ctx);
}
}
}
4 changes: 2 additions & 2 deletions src/Riok.Mapperly/Helpers/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ internal static IEnumerable<ITypeSymbol> 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);
}
}
55 changes: 55 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Riok.Mapperly.Diagnostics;

Check failure on line 1 in test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Using directive is unnecessary.

namespace Riok.Mapperly.Tests.Mapping;

public class EnumerableCustomTest
Expand Down Expand Up @@ -381,4 +383,57 @@ public void CustomCollectionToCustomCollectionWithObjectFactory()
"""
);
}

[Fact]
public void CustomCollectionToGetOnlyICollection()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public C Value { get; } }",
"class B { public ICollection<int> Value { get; } }",
"class C : IEnumerable<int> { 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<int> Value { get; } }",
"class C : IEnumerable<int> { 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;
"""
);
}
}

0 comments on commit 9ed9d0e

Please sign in to comment.