Skip to content

Commit

Permalink
fix: Respect AllowNullPropertyAssignment correctly when mapping code …
Browse files Browse the repository at this point in the history
…is not a direct assignment
  • Loading branch information
latonz committed Nov 22, 2023
1 parent 42d3e26 commit d00b439
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
Expand All @@ -22,6 +23,13 @@ public void AddNullDelegateMemberAssignmentMapping(IMemberAssignmentMapping memb
var nullConditionSourcePath = new MemberPath(memberMapping.SourcePath.PathWithoutTrailingNonNullable().ToList());
var container = GetOrCreateNullDelegateMappingForPath(nullConditionSourcePath);
AddMemberAssignmentMapping(container, memberMapping);

// set target member to null if null assignments are allowed
// and the source is null
if (BuilderContext.MapperConfiguration.AllowNullPropertyAssignment && memberMapping.TargetPath.Member.Type.IsNullable())
{
container.AddNullMemberAssignment(SetterMemberPath.Build(BuilderContext, memberMapping.TargetPath));
}
}

private void AddMemberAssignmentMapping(IMemberAssignmentMappingContainer container, IMemberAssignmentMapping mapping)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bool needsNullSafeAccess
{
private readonly GetterMemberPath _nullConditionalSourcePath = nullConditionalSourcePath;
private readonly bool _throwInsteadOfConditionalNullMapping = throwInsteadOfConditionalNullMapping;
private readonly List<SetterMemberPath> _targetsToSetNull = new();

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax targetAccess)
{
Expand All @@ -26,17 +27,16 @@ public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx,
// else
// throw ...
var sourceNullConditionalAccess = _nullConditionalSourcePath.BuildAccess(ctx.Source, false, needsNullSafeAccess, true);
var nameofSourceAccess = _nullConditionalSourcePath.BuildAccess(ctx.Source, false, false, true);
var condition = IsNotNull(sourceNullConditionalAccess);
var conditionCtx = ctx.AddIndentation();
var trueClause = base.Build(conditionCtx, targetAccess);
var elseClause = _throwInsteadOfConditionalNullMapping
? new[] { conditionCtx.SyntaxFactory.ExpressionStatement(ThrowArgumentNullException(nameofSourceAccess)) }
: null;
var elseClause = BuildElseClause(conditionCtx, targetAccess);
var ifExpression = ctx.SyntaxFactory.If(condition, trueClause, elseClause);
return new[] { ifExpression };
}

public void AddNullMemberAssignment(SetterMemberPath targetPath) => _targetsToSetNull.Add(targetPath);

public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
Expand Down Expand Up @@ -70,4 +70,19 @@ protected bool Equals(MemberNullDelegateAssignmentMapping other)
return _nullConditionalSourcePath.Equals(other._nullConditionalSourcePath)
&& _throwInsteadOfConditionalNullMapping == other._throwInsteadOfConditionalNullMapping;
}

private IEnumerable<StatementSyntax>? BuildElseClause(TypeMappingBuildContext ctx, ExpressionSyntax targetAccess)
{
if (_throwInsteadOfConditionalNullMapping)
{
// throw new ArgumentNullException
var nameofSourceAccess = _nullConditionalSourcePath.BuildAccess(ctx.Source, false, false, true);
return new[] { ctx.SyntaxFactory.ExpressionStatement(ThrowArgumentNullException(nameofSourceAccess)) };
}

// target.A = null;
return _targetsToSetNull.Count == 0
? null
: _targetsToSetNull.Select(x => ctx.SyntaxFactory.ExpressionStatement(x.BuildAssignment(targetAccess, NullLiteral())));
}
}
8 changes: 4 additions & 4 deletions src/Riok.Mapperly/Symbols/SetterMemberPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private static (IMappableMember, bool) BuildMemberSetter(MappingBuilderContext c
return (new MethodAccessorMember(member, unsafeGetAccessor.MethodName, methodRequiresParameter: true), true);
}

public ExpressionSyntax BuildAssignment(ExpressionSyntax? baseAccess, ExpressionSyntax sourceValue, bool coalesceAssignment = false)
public ExpressionSyntax BuildAssignment(ExpressionSyntax? baseAccess, ExpressionSyntax valueToAssign, bool coalesceAssignment = false)
{
IEnumerable<IMappableMember> path = Path;

Expand All @@ -73,16 +73,16 @@ public ExpressionSyntax BuildAssignment(ExpressionSyntax? baseAccess, Expression
Debug.Assert(!IsMethod);

// target.Value ??= mappedValue;
return CoalesceAssignment(memberPath, sourceValue);
return CoalesceAssignment(memberPath, valueToAssign);
}

if (IsMethod)
{
// target.SetValue(source.Value);
return Invocation(memberPath, sourceValue);
return Invocation(memberPath, valueToAssign);
}

// target.Value = source.Value;
return Assignment(memberPath, sourceValue);
return Assignment(memberPath, valueToAssign);
}
}
13 changes: 13 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/ObjectPropertyFlatteningTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ public void AutoFlattenedMultiplePropertiesPathDisabledNullable()
{
target.ValueId = source.Value.Id.ToString();
}
else
{
target.ValueId = null;
}
target.ValueName = source.Value?.Name;
return target;
"""
Expand Down Expand Up @@ -516,8 +520,17 @@ public void ManualNestedPropertyNullablePath()
target.Value2.Value2.Id2 = source.Value1.Value1.Id1;
target.Value2.Value2.Id20 = source.Value1.Value1.Id10;
}
else
{
target.Value2.Value2.Id2 = null;
target.Value2.Value2.Id20 = null;
}
target.Value2.Id200 = source.Value1.Id100;
}
else
{
target.Value2.Id200 = null;
}
return target;
"""
);
Expand Down
37 changes: 37 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ public void NullableClassToNullableClassProperty()
{
target.Value = MapToD(source.Value);
}
else
{
target.Value = null;
}
return target;
"""
);
Expand Down Expand Up @@ -440,6 +444,39 @@ public void NullableClassPropertyToDisabledNullableProperty()
{
target.Value = MapToD(source.Value);
}
else
{
target.Value = null;
}
return target;
"""
);
}

[Fact]
public void NullableValueTypeToOtherNullableValueType()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
"class A { public float? Value { get; set; } }",
"class B { public decimal? Value { get; set; } }"
);

TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
var target = new global::B();
if (source.Value != null)
{
target.Value = new decimal(source.Value.Value);
}
else
{
target.Value = null;
}
return target;
"""
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Mapper
{
target.Value = MapToICollection(source.Value);
}
else
{
target.Value = null;
}
return target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Mapper
{
target.Value = MapToIReadOnlyCollection(source.Value);
}
else
{
target.Value = null;
}
return target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Mapper
{
target.Value = MapToIReadOnlyCollection(source.Value);
}
else
{
target.Value = null;
}
return target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Mapper
{
target.Value = MapToDArray(source.Value);
}
else
{
target.Value = null;
}
return target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public partial class Mapper
{
target.Value = MapToD(source.Value);
}
else
{
target.Value = null;
}
return target;
}

Expand Down

0 comments on commit d00b439

Please sign in to comment.