Skip to content
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
8 changes: 4 additions & 4 deletions src/Mammoth.LiteMapper.Generator/LiteMapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -743,15 +743,15 @@ private static MappingModel CreateUpdateMappingModel(IMethodSymbol method, Compi
var escapedSourceName = EscapeIdentifier(source.Name);
var sourcePath = selected?.Expression ?? match.Member.Name;
var sourceMayBeNull = !rootArgument && (selected != null ? SourcePathMayBeNull(selected) : SourceMayBeNull(match.Member));
var throwCaptureName = sourceMayBeNull && selected != null && !options.IgnoreNullSourceMembers &&
var throwCaptureName = sourceMayBeNull && !options.IgnoreNullSourceMembers &&
!IsMaybeNull(updater.Parameters[0].Type) && options.NullableMismatch != NullableMismatchPolicyError
? CreateExpressionCaptureName(method, targetMember.Name, selected.Expression)
? CreateExpressionCaptureName(method, targetMember.Name, selected?.Expression ?? match.Member.Name)
: null;
var argument = rootArgument
? escapedSourceName
: selected != null
? sourcePathCaptureName ?? throwCaptureName ?? BuildNullSafeSourcePathExpression(source.Name, selected)
: escapedSourceName + "." + EscapeIdentifier(match.Member.Name);
: throwCaptureName ?? escapedSourceName + "." + EscapeIdentifier(match.Member.Name);
string? updaterGuard = null;
PreconditionModel? updaterPrecondition = null;
if (sourceMayBeNull)
Expand All @@ -769,7 +769,7 @@ private static MappingModel CreateUpdateMappingModel(IMethodSymbol method, Compi
}

var nullCheck = throwCaptureName != null
? "!(" + BuildNullSafeSourcePathExpression(source.Name, selected!) + " is { } " + throwCaptureName + ")"
? "!(" + (selected != null ? BuildNullSafeSourcePathExpression(source.Name, selected) : escapedSourceName + "." + EscapeIdentifier(match.Member.Name)) + " is { } " + throwCaptureName + ")"
: selected == null ? argument + " == null" : BuildNullCheck(source.Name, selected);
if (nullCheck != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,70 @@ public void ThrowPolicyReportsTheNullNestedUpdaterSourcePath()
StringAssert.Contains((string)Execute(result)!, "Child", "The runtime mismatch must identify the null member path.");
}

[TestMethod]
public void ThrowPolicyEvaluatesDirectNullableNestedUpdaterSourceOnce()
{
var result = Run(@"
#nullable enable
using System;
using Mammoth.LiteMapper;

[LiteMapper(NullableMismatch = NullableMismatchPolicy.Throw)]
public static partial class Mapper
{
public static int UpdaterCalls;

public static partial void Apply(Source source, Target target);

[DefaultMapping]
private static void ApplyChild(ChildSource source, ChildTarget target)
{
UpdaterCalls++;
target.Value = source.Value;
}
}

public sealed class Source
{
private readonly ChildSource? child;
public static int ChildReads;

public Source(ChildSource? child) { this.child = child; }
public ChildSource? Child { get { ChildReads++; return child; } }
}

public sealed class ChildSource { public int Value { get; set; } }
public sealed class ChildTarget { public int Value { get; set; } }
public sealed class Target { public ChildTarget Child { get; } = new ChildTarget(); }

public static class Probe
{
public static bool Run()
{
Source.ChildReads = Mapper.UpdaterCalls = 0;
var target = new Target();
Mapper.Apply(new Source(new ChildSource { Value = 3 }), target);
var present = target.Child.Value == 3 && Source.ChildReads == 1 && Mapper.UpdaterCalls == 1;

Source.ChildReads = Mapper.UpdaterCalls = 0;
try
{
Mapper.Apply(new Source(null), target);
return false;
}
catch (InvalidOperationException exception)
{
return present && exception.Message.Contains(""Child"", StringComparison.Ordinal) &&
Source.ChildReads == 1 && Mapper.UpdaterCalls == 0;
}
}
}
");

Assert.AreEqual(true, Execute(result),
"A direct nullable source passed to a Throw-policy nested updater must be evaluated once for present and null values.");
}

[TestMethod]
public void ReturningUpdaterCreatesAndAssignsANullableWritableChild()
{
Expand Down