Skip to content

Commit

Permalink
fix: lambdas should use correct scopes to generate names (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed May 8, 2023
1 parent 5d32b56 commit e5d9bb2
Show file tree
Hide file tree
Showing 13 changed files with 61 additions and 52 deletions.
14 changes: 4 additions & 10 deletions src/Riok.Mapperly/Descriptors/Mappings/DerivedTypeMapping.cs
Expand Up @@ -11,7 +11,6 @@ namespace Riok.Mapperly.Descriptors.Mappings;
/// </summary>
public class DerivedTypeMapping : TypeMapping
{
private const string TypeArmVariableName = "x";
private const string GetTypeMethodName = "GetType";

private readonly IReadOnlyDictionary<ITypeSymbol, ITypeMapping> _typeMappings;
Expand All @@ -35,20 +34,15 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
);

// source switch { A x => MapToA(x), B x => MapToB(x) }
var typeArmVariableName = ctx.NameBuilder.New(TypeArmVariableName);
var arms = _typeMappings.Select(x => BuildSwitchArm(ctx, typeArmVariableName, x.Key, x.Value)).Append(fallbackArm);
var (typeArmContext, typeArmVariableName) = ctx.WithNewSource();
var arms = _typeMappings.Select(x => BuildSwitchArm(typeArmVariableName, x.Key, x.Value.Build(typeArmContext))).Append(fallbackArm);
return SwitchExpression(ctx.Source).WithArms(CommaSeparatedList(arms, true));
}

private SwitchExpressionArmSyntax BuildSwitchArm(
TypeMappingBuildContext ctx,
string typeArmVariableName,
ITypeSymbol type,
ITypeMapping typeMapping
)
private SwitchExpressionArmSyntax BuildSwitchArm(string typeArmVariableName, ITypeSymbol type, ExpressionSyntax mapping)
{
// A x => MapToA(x),
var declaration = DeclarationPattern(FullyQualifiedIdentifier(type), SingleVariableDesignation(Identifier(typeArmVariableName)));
return SwitchExpressionArm(declaration, typeMapping.Build(ctx.WithSource(typeArmVariableName)));
return SwitchExpressionArm(declaration, mapping);
}
}
Expand Up @@ -34,8 +34,8 @@ public class ForEachAddEnumerableExistingTargetMapping : ExistingTargetMapping

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
{
var loopItemVariableName = ctx.NameBuilder.New(LoopItemVariableName);
var convertedSourceItemExpression = _elementMapping.Build(ctx.WithSource(loopItemVariableName));
var (loopItemCtx, loopItemVariableName) = ctx.WithNewSource(LoopItemVariableName);
var convertedSourceItemExpression = _elementMapping.Build(loopItemCtx);
var addMethod = MemberAccess(target, _insertMethodName);

if (_ensureCapacityBuilder != null)
Expand Down
10 changes: 3 additions & 7 deletions src/Riok.Mapperly/Descriptors/Mappings/LinqConstructorMapping.cs
Expand Up @@ -10,8 +10,6 @@ namespace Riok.Mapperly.Descriptors.Mappings;
/// </summary>
public class LinqConstructorMapping : TypeMapping
{
private const string LambdaParamName = "x";

private readonly ITypeMapping _elementMapping;
private readonly IMethodSymbol? _selectMethod;

Expand All @@ -24,16 +22,14 @@ public LinqConstructorMapping(ITypeSymbol sourceType, ITypeSymbol targetType, IT

public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
var scopedNameBuilder = ctx.NameBuilder.NewScope();
var lambdaParamName = scopedNameBuilder.New(LambdaParamName);

ExpressionSyntax mappedSource;

// Select / Map if needed
if (_selectMethod != null)
{
var sourceMapExpression = _elementMapping.Build(ctx.WithSource(lambdaParamName));
var convertLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(sourceMapExpression);
var (lambdaCtx, lambdaSourceName) = ctx.WithNewScopedSource();
var sourceMapExpression = _elementMapping.Build(lambdaCtx);
var convertLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaSourceName))).WithExpressionBody(sourceMapExpression);
mappedSource = StaticInvocation(_selectMethod, ctx.Source, convertLambda);
}
else
Expand Down
19 changes: 8 additions & 11 deletions src/Riok.Mapperly/Descriptors/Mappings/LinqDictionaryMapping.cs
Expand Up @@ -10,8 +10,6 @@ namespace Riok.Mapperly.Descriptors.Mappings;
/// </summary>
public class LinqDicitonaryMapping : TypeMapping
{
private const string LambdaParamName = "x";

private const string KeyPropertyName = nameof(KeyValuePair<object, object>.Key);
private const string ValuePropertyName = nameof(KeyValuePair<object, object>.Value);

Expand All @@ -35,21 +33,20 @@ ITypeMapping valueMapping

public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
var scopedNameBuilder = ctx.NameBuilder.NewScope();
var lambdaParamName = scopedNameBuilder.New(LambdaParamName);

// if key and value types do not change then use a simple call
// ie: source.ToImmutableDictionary();
if (_keyMapping.IsSynthetic && _valueMapping.IsSynthetic)
return StaticInvocation(_collectMethod, ctx.Source);

// create expressions mapping the key and value and then create the final expression
// ie: source.ToImmutableDictionary(x => x.Key, x=> (int)x.Value);
var keyMapExpression = _keyMapping.Build(ctx.WithSource(MemberAccess(lambdaParamName, KeyPropertyName)));
var keyExpression = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(keyMapExpression);

var valueMapExpression = _valueMapping.Build(ctx.WithSource(MemberAccess(lambdaParamName, ValuePropertyName)));
var valueExpression = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(valueMapExpression);
// ie: source.ToImmutableDictionary(x => x.Key, x => (int)x.Value);
var (keyLambdaCtx, keyLambdaParamName) = ctx.WithNewScopedSource(src => MemberAccess(src, KeyPropertyName));
var keyMapExpression = _keyMapping.Build(keyLambdaCtx);
var keyExpression = SimpleLambdaExpression(Parameter(Identifier(keyLambdaParamName))).WithExpressionBody(keyMapExpression);

var (valueLambdaCtx, valueLambdaParamName) = ctx.WithNewScopedSource(src => MemberAccess(src, ValuePropertyName));
var valueMapExpression = _valueMapping.Build(valueLambdaCtx);
var valueExpression = SimpleLambdaExpression(Parameter(Identifier(valueLambdaParamName))).WithExpressionBody(valueMapExpression);

return StaticInvocation(_collectMethod, ctx.Source, keyExpression, valueExpression);
}
Expand Down
10 changes: 3 additions & 7 deletions src/Riok.Mapperly/Descriptors/Mappings/LinqEnumerableMapping.cs
Expand Up @@ -10,8 +10,6 @@ namespace Riok.Mapperly.Descriptors.Mappings;
/// </summary>
public class LinqEnumerableMapping : TypeMapping
{
private const string LambdaParamName = "x";

private readonly ITypeMapping _elementMapping;
private readonly IMethodSymbol? _selectMethod;
private readonly IMethodSymbol? _collectMethod;
Expand All @@ -32,16 +30,14 @@ public class LinqEnumerableMapping : TypeMapping

public override ExpressionSyntax Build(TypeMappingBuildContext ctx)
{
var scopedNameBuilder = ctx.NameBuilder.NewScope();
var lambdaParamName = scopedNameBuilder.New(LambdaParamName);

ExpressionSyntax mappedSource;

// Select / Map if needed
if (_selectMethod != null)
{
var sourceMapExpression = _elementMapping.Build(ctx.WithSource(lambdaParamName));
var convertLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(sourceMapExpression);
var (lambdaCtx, lambdaSourceName) = ctx.WithNewScopedSource();
var sourceMapExpression = _elementMapping.Build(lambdaCtx);
var convertLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaSourceName))).WithExpressionBody(sourceMapExpression);
mappedSource = StaticInvocation(_selectMethod, ctx.Source, convertLambda);
}
else
Expand Down
Expand Up @@ -11,8 +11,6 @@ namespace Riok.Mapperly.Descriptors.Mappings;
/// </summary>
public class QueryableProjectionMapping : MethodMapping
{
private const string SelectLambdaParameterName = "x";

private const string QueryableReceiverName = "System.Linq.Queryable";
private const string SelectMethodName = nameof(Queryable.Select);

Expand All @@ -30,11 +28,10 @@ public override IEnumerable<StatementSyntax> BuildBody(TypeMappingBuildContext c
// #nullable disable
// return System.Linq.Enumerable.Select(source, x => ...);
// #nullable enable
var scopedNameBuilder = ctx.NameBuilder.NewScope();
var lambdaParamName = scopedNameBuilder.New(SelectLambdaParameterName);
var (lambdaCtx, lambdaSourceName) = ctx.WithNewScopedSource();

var delegateMapping = _delegateMapping.Build(ctx.WithSource(IdentifierName(lambdaParamName)));
var projectionLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(delegateMapping);
var delegateMapping = _delegateMapping.Build(lambdaCtx);
var projectionLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaSourceName))).WithExpressionBody(delegateMapping);
var select = StaticInvocation(QueryableReceiverName, SelectMethodName, ctx.Source, projectionLambda);
return new[]
{
Expand Down
33 changes: 31 additions & 2 deletions src/Riok.Mapperly/Descriptors/Mappings/TypeMappingBuildContext.cs
Expand Up @@ -6,6 +6,8 @@ namespace Riok.Mapperly.Descriptors.Mappings;

public class TypeMappingBuildContext
{
private const string DefaultSourceName = "x";

public TypeMappingBuildContext(string source, string? referenceHandler, UniqueNameBuilder nameBuilder)
: this(IdentifierName(source), referenceHandler == null ? null : IdentifierName(referenceHandler), nameBuilder) { }

Expand All @@ -22,9 +24,36 @@ private TypeMappingBuildContext(ExpressionSyntax source, ExpressionSyntax? refer

public ExpressionSyntax? ReferenceHandler { get; }

public TypeMappingBuildContext WithSource(ExpressionSyntax source) => new(source, ReferenceHandler, NameBuilder);
/// <summary>
/// Creates a new scoped name builder,
/// builds the name of the source in this new scope
/// and creates a new context with the new source.
/// </summary>
/// <returns>The new context and the scoped name of the source.</returns>
public (TypeMappingBuildContext Context, string SourceName) WithNewScopedSource() => WithNewScopedSource(IdentifierName);

/// <summary>
/// Creates a new scoped name builder,
/// builds the name of the source in this new scope
/// and creates a new context with the new source.
/// </summary>
/// <param name="sourceBuilder">A function to build the source access for the new context.</param>
/// <returns>The new context and the scoped name of the source.</returns>
public (TypeMappingBuildContext Context, string SourceName) WithNewScopedSource(Func<string, ExpressionSyntax> sourceBuilder)
{
var scopedNameBuilder = NameBuilder.NewScope();
var scopedSourceName = scopedNameBuilder.New(DefaultSourceName);
var ctx = new TypeMappingBuildContext(sourceBuilder(scopedSourceName), ReferenceHandler, scopedNameBuilder);
return (ctx, scopedSourceName);
}

public TypeMappingBuildContext WithSource(string source) => WithSource(IdentifierName(source));
public (TypeMappingBuildContext Context, string SourceName) WithNewSource(string sourceName = DefaultSourceName)
{
var scopedSourceName = NameBuilder.New(sourceName);
return (WithSource(IdentifierName(scopedSourceName)), scopedSourceName);
}

public TypeMappingBuildContext WithSource(ExpressionSyntax source) => new(source, ReferenceHandler, NameBuilder);

public TypeMappingBuildContext WithRefHandler(string refHandler) => WithRefHandler(IdentifierName(refHandler));

Expand Down
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Helpers/UniqueNameBuilder.cs
Expand Up @@ -9,7 +9,7 @@ public UniqueNameBuilder()
_usedNames = new HashSet<string>();
}

public UniqueNameBuilder(IEnumerable<string> usedNames)
private UniqueNameBuilder(IEnumerable<string> usedNames)
{
_usedNames = new HashSet<string>(usedNames);
}
Expand Down
Expand Up @@ -6,7 +6,7 @@ public static partial class ProjectionMapper
public static partial global::System.Linq.IQueryable<global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection> ProjectToDto(this global::System.Linq.IQueryable<global::Riok.Mapperly.IntegrationTests.Models.TestObjectProjection> q)
{
#nullable disable
return System.Linq.Queryable.Select(q, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.IntValue })) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)System.Enum.Parse(typeof(global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName), x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly), ManuallyMapped = MapManual(x.ManuallyMapped) });
return System.Linq.Queryable.Select(q, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x1.IntValue })) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)System.Enum.Parse(typeof(global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName), x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly), ManuallyMapped = MapManual(x.ManuallyMapped) });
#nullable enable
}

Expand Down
Expand Up @@ -9,8 +9,8 @@ public static partial class ProjectionMapper
return System.Linq.Queryable.Select(q, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue)
{IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto()
{IntValue = x.NestedNullable.IntValue} : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto()
{IntValue = x.NestedNullableTargetNotNullable.IntValue} : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto()
{IntValue = x.IntValue})) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse<global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName>(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto()
{IntValue = x.NestedNullableTargetNotNullable.IntValue} : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto()
{IntValue = x1.IntValue})) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse<global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName>(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto()
{SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue} : default, DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly), ManuallyMapped = MapManual(x.ManuallyMapped)});
#nullable enable
}
Expand Down

0 comments on commit e5d9bb2

Please sign in to comment.