Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore capacity related diagnostics for system collection types when mapping from custom type #1531

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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 @@ -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<string> _sourceCountAlias =
[
Expand All @@ -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();
Expand Down Expand Up @@ -72,6 +73,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 +118,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);
}
}
53 changes: 53 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumerableCustomTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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;
"""
);
}
}