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

support capacity settable members #1526

Merged
merged 1 commit into from
Oct 11, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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));
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Generates an <see cref="EnsureCapacityInfo"/> of types <see cref="EnsureCapacityNonEnumerated"/> or <see cref="EnsureCapacityMember"/> depending on type information.
/// Generates an <see cref="ICapacitySetter"/> of types <see cref="NonEnumeratedCapacitySetter"/> or <see cref="SimpleCapacitySetter"/> depending on type information.
/// </summary>
public static class EnsureCapacityBuilder
public static class CapacitySetterBuilder
{
private const string EnsureCapacityName = "EnsureCapacity";
private const string CapacityMemberName = "Capacity";
private const string TryGetNonEnumeratedCountMethodName = "TryGetNonEnumeratedCount";

public static EnsureCapacityInfo? TryBuildEnsureCapacity(
public static ICapacitySetter? TryBuildCapacitySetter(
MappingBuilderContext ctx,
CollectionInfos collectionInfos,
bool includeTargetCount
)
{
var source = collectionInfos.Source;
var target = collectionInfos.Target;
var capacityMethod = ctx
.SymbolAccessor.GetAllMethods(target.Type, EnsureCapacityName)
.FirstOrDefault(x => x.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }] && !x.IsStatic);

// if EnsureCapacity is not available then return null
if (capacityMethod == null)
var capacitySetter = BuildCapacitySetter(ctx, target);
if (capacitySetter == null)
return null;

var targetCount = includeTargetCount ? target.CountMember?.BuildGetter(ctx.UnsafeAccessorContext) : null;
Expand All @@ -32,7 +28,7 @@ bool includeTargetCount
if (source.CountIsKnown)
{
var sourceCount = source.CountMember.BuildGetter(ctx.UnsafeAccessorContext);
return new EnsureCapacityMember(targetCount, sourceCount);
return new SimpleCapacitySetter(capacitySetter, targetCount, sourceCount);
}

var nonEnumeratedCountMethod = ctx
Expand All @@ -49,6 +45,21 @@ bool includeTargetCount
return null;

// if source does not have a count use GetNonEnumeratedCount, calling EnsureCapacity if count is available
return new EnsureCapacityNonEnumerated(targetCount, nonEnumeratedCountMethod);
return new NonEnumeratedCapacitySetter(capacitySetter, targetCount, nonEnumeratedCountMethod);
}

private static ICapacityMemberSetter? BuildCapacitySetter(MappingBuilderContext ctx, CollectionInfo target)
{
var ensureCapacityMethod = ctx
.SymbolAccessor.GetAllMethods(target.Type, EnsureCapacityMethodSetter.EnsureCapacityMethodName)
.FirstOrDefault(x => x.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }] && !x.IsStatic);
if (ensureCapacityMethod != null)
return EnsureCapacityMethodSetter.Instance;

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 null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Symbols.Members;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Ensures the capacity of a collection by calling `EnsureCapacity(int)`
/// </summary>
internal class EnsureCapacityMethodSetter : ICapacityMemberSetter
{
public static readonly EnsureCapacityMethodSetter Instance = new();

public const string EnsureCapacityMethodName = "EnsureCapacity";

private EnsureCapacityMethodSetter() { }

public bool SupportsCoalesceAssignment => false;

public IMappableMember? TargetCapacity => null;

public ExpressionSyntax BuildAssignment(ExpressionSyntax? baseAccess, ExpressionSyntax valueToAssign, bool coalesceAssignment = false)
{
if (baseAccess == null)
throw new ArgumentNullException(nameof(baseAccess));

return InvocationWithoutIndention(MemberAccess(baseAccess, EnsureCapacityMethodName), valueToAssign);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Sets the capacity of a collection to the provided count.
/// </summary>
public interface ICapacityMemberSetter : IMemberSetter
{
IMappableMember? TargetCapacity { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Sets the capacity of a collection to the calculated count.
/// </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 @@ -5,11 +5,11 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Represents a call to EnsureCapacity on a collection where there is an attempt
/// to get the number of elements in the source collection without enumeration,
/// to get the number of elements in the source collection without enumeration,
/// calling EnsureCapacity if it is available.
/// </summary>
/// <remarks>
Expand All @@ -18,26 +18,29 @@ namespace Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
/// target.EnsureCapacity(sourceCount + target.Count);
/// </code>
/// </remarks>
public class EnsureCapacityNonEnumerated(IMemberGetter? targetAccessor, IMethodSymbol getNonEnumeratedMethod) : EnsureCapacityInfo
public class NonEnumeratedCapacitySetter(
ICapacityMemberSetter capacitySetter,
IMemberGetter? targetAccessor,
IMethodSymbol getNonEnumeratedMethod
) : ICapacitySetter
{
private const string SourceCountVariableName = "sourceCount";

public override StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
var targetCount = targetAccessor?.BuildAccess(target);
public IMappableMember? CapacityTargetMember => capacitySetter.TargetCapacity;

public StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
var sourceCountName = ctx.NameBuilder.New(SourceCountVariableName);
ExpressionSyntax count = IdentifierName(sourceCountName);
if (targetAccessor != null)
{
count = Add(count, targetAccessor.BuildAccess(target));
}

var enumerableArgument = Argument(ctx.Source);
var outVarArgument = OutVarArgument(sourceCountName);

var getNonEnumeratedInvocation = ctx.SyntaxFactory.StaticInvocation(getNonEnumeratedMethod, enumerableArgument, outVarArgument);
var ensureCapacity = EnsureCapacityStatement(
ctx.SyntaxFactory.AddIndentation(),
target,
IdentifierName(sourceCountName),
targetCount
);
return ctx.SyntaxFactory.If(getNonEnumeratedInvocation, ensureCapacity);
var setCapacity = ctx.SyntaxFactory.AddIndentation().ExpressionStatement(capacitySetter.BuildAssignment(target, count));
return ctx.SyntaxFactory.If(getNonEnumeratedInvocation, setCapacity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Symbols.Members;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Enumerables.Capacity;

/// <summary>
/// Represents setting the capacity on a collection where both the source and targets counts are accessible.
/// </summary>
/// <remarks>
/// <code>
/// target.EnsureCapacity(source.Length + target.Count);
/// // or
/// target.Capacity = source.Length + target.Count;
/// </code>
/// </remarks>
public class SimpleCapacitySetter(ICapacityMemberSetter capacitySetter, IMemberGetter? targetAccessor, IMemberGetter sourceAccessor)
: ICapacitySetter
{
public IMappableMember? CapacityTargetMember => capacitySetter.TargetCapacity;

public StatementSyntax Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
var count = sourceAccessor.BuildAccess(ctx.Source);
if (targetAccessor != null)
{
count = Add(count, targetAccessor.BuildAccess(target));
}

return ctx.SyntaxFactory.ExpressionStatement(capacitySetter.BuildAssignment(target, count));
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Descriptors.Enumerables.EnsureCapacity;
using Riok.Mapperly.Descriptors.Enumerables.Capacity;
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Helpers;
Expand All @@ -10,6 +10,13 @@ internal static class EnumerableMappingBodyBuilder
{
private const string SystemNamespaceName = "System";

private static readonly IReadOnlyCollection<string> _sourceCountAlias =
[
nameof(Array.Length),
nameof(List<object>.Count),
nameof(List<object>.Capacity),
];

public static void BuildMappingBody(MappingBuilderContext ctx, INewInstanceEnumerableMapping mapping)
{
var mappingCtx = new NewInstanceContainerBuilderContext<INewInstanceEnumerableMapping>(ctx, mapping);
Expand All @@ -26,9 +33,14 @@ public static void BuildMappingBody(MappingBuilderContext ctx, IEnumerableMappin
InitContext(mappingCtx);

// include the target count as the target could already include elements
if (EnsureCapacityBuilder.TryBuildEnsureCapacity(ctx, mapping.CollectionInfos, true) is { } ensureCapacity)
if (CapacitySetterBuilder.TryBuildCapacitySetter(ctx, mapping.CollectionInfos, true) is { } capacitySetter)
{
mapping.AddEnsureCapacity(ensureCapacity);
if (capacitySetter.CapacityTargetMember != null)
{
mappingCtx.IgnoreMembers(capacitySetter.CapacityTargetMember);
}

mapping.AddCapacitySetter(capacitySetter);
}

ObjectMemberMappingBodyBuilder.BuildMappingBody(mappingCtx);
Expand All @@ -47,7 +59,7 @@ private static void InitContext<T>(MembersMappingBuilderContext<T> ctx)
private static void IgnoreSystemMembers<T>(IMembersBuilderContext<T> ctx, ITypeSymbol type)
where T : IMapping
{
// ignore all members of collection classes of the System.Private.CoreLib assembly or of arrays
// ignore all members of collection classes of the System.Private.CoreLib assembly
// as these are considered mapped by the enumerable mapping itself
// these members can still be mapped with an explicit configuration.
var systemType = type.WalkTypeHierarchy().FirstOrDefault(x => x.IsArrayType() || x.IsInRootNamespace(SystemNamespaceName));
Expand All @@ -66,9 +78,10 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext<INewInsta
// named with a well known "count" name
if (ctx.Mapping.CollectionInfos.Source.CountIsKnown)
{
ctx.TryAddSourceMemberAlias(nameof(List<object>.Capacity), ctx.Mapping.CollectionInfos.Source.CountMember);
ctx.TryAddSourceMemberAlias(nameof(List<object>.Count), ctx.Mapping.CollectionInfos.Source.CountMember);
ctx.TryAddSourceMemberAlias(nameof(Array.Length), ctx.Mapping.CollectionInfos.Source.CountMember);
foreach (var countAlias in _sourceCountAlias)
{
ctx.TryAddSourceMemberAlias(countAlias, ctx.Mapping.CollectionInfos.Source.CountMember);
}
}

// always prefer parameterized constructor for system collections (to map capacity correctly)
Expand All @@ -87,15 +100,20 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext<INewInsta
// do not include the target count as the instance is just created by the ctor
if (
!countIsMapped
&& EnsureCapacityBuilder.TryBuildEnsureCapacity(ctx.BuilderContext, ctx.Mapping.CollectionInfos, false) is { } ensureCapacity
&& CapacitySetterBuilder.TryBuildCapacitySetter(ctx.BuilderContext, ctx.Mapping.CollectionInfos, false) is { } capacitySetter
)
{
if (ctx.Mapping.CollectionInfos.Source.CountIsKnown)
{
ctx.IgnoreMembers(ctx.Mapping.CollectionInfos.Source.CountMember);
}

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

ctx.Mapping.AddCapacitySetter(capacitySetter);
}
}
}
Loading