diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersContainerBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersContainerBuilderContext.cs index 6894c8fbbb..a25d314e89 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersContainerBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersContainerBuilderContext.cs @@ -15,7 +15,7 @@ public class MembersContainerBuilderContext(MappingBuilderContext builderCont where T : IMemberAssignmentTypeMapping { private readonly Dictionary _nullDelegateMappings = new(); - private readonly HashSet _initializedNullableTargetPaths = []; + private readonly HashSet<(IMemberAssignmentMappingContainer, MemberPath)> _initializedNullableTargetPaths = []; public void AddMemberAssignmentMapping(IMemberAssignmentMapping memberMapping) => AddMemberAssignmentMapping(Mapping, memberMapping); @@ -79,7 +79,7 @@ private void AddMemberAssignmentMapping(IMemberAssignmentMappingContainer contai // the target should be non-null after this assignment and can be set as initialized. if (!mapping.MemberInfo.IsSourceNullable && mapping.MemberInfo.TargetMember.MemberType.IsNullable()) { - _initializedNullableTargetPaths.Add(mapping.MemberInfo.TargetMember); + _initializedNullableTargetPaths.Add((container, mapping.MemberInfo.TargetMember)); } } @@ -93,7 +93,10 @@ private void AddNullMemberInitializers(IMemberAssignmentMappingContainer contain if (!nullablePath.Member.CanSet) continue; - if (!_initializedNullableTargetPaths.Add(nullablePath)) + if (_initializedNullableTargetPaths.Contains((Mapping, nullablePath))) + continue; + + if (!_initializedNullableTargetPaths.Add((container, nullablePath))) continue; if (!BuilderContext.InstanceConstructors.TryBuild(BuilderContext.Source, type, out var ctor)) diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs index 06c74959d3..ac87a5cc19 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyNullableTest.cs @@ -588,6 +588,45 @@ public partial B Map(A a) ); } + [Fact] + public void NullableNestedMembersShouldInitializeWithNoNullAssignment() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapProperty("NullableValue1", "V.NullableValue1")] + [MapProperty("NullableValue2", "V.NullableValue2")] + public partial B Map(A a) + """, + TestSourceBuilderOptions.Default with + { + AllowNullPropertyAssignment = false, + }, + "class A { public int? NullableValue1 { get; set; } public int? NullableValue2 { get; set; } }", + "class B { public C? V { get; set; } }", + "class C { public int? NullableValue1 { get; set; } public int? NullableValue2 { get; set; } }" + ); + + TestHelper + .GenerateMapper(source) + .Should() + .HaveMapMethodBody( + """ + var target = new global::B(); + if (a.NullableValue1 != null) + { + target.V ??= new global::C(); + target.V.NullableValue1 = a.NullableValue1.Value; + } + if (a.NullableValue2 != null) + { + target.V ??= new global::C(); + target.V.NullableValue2 = a.NullableValue2.Value; + } + return target; + """ + ); + } + [Fact] public void NullableClassToNullableClassPropertyThrowShouldSetNull() {