Skip to content
Open
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
Expand Up @@ -19,7 +19,10 @@ IExistingTargetMapping delegateMapping
{
private const string NullableValueProperty = nameof(Nullable<>.Value);

public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target)
public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target) =>
Build(ctx, target, ctx.Source);

public IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, ExpressionSyntax target, ExpressionSyntax sourceNullCheckAccess)
{
// if the source or target type is nullable, add a null guard.
if (!SourceType.IsNullable() && !TargetType.IsNullable())
Expand All @@ -32,7 +35,7 @@ public override IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx,
return [];

// if (source != null && target != null) { body }
var condition = IfNoneNull((SourceType, ctx.Source), (TargetType, target));
var condition = IfNoneNull((SourceType, sourceNullCheckAccess), (TargetType, target));
var ifStatement = ctx.SyntaxFactory.If(condition, body);
return [ifStatement];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ public IEnumerable<StatementSyntax> Build(TypeMappingBuildContext ctx, Expressio
{
var source = sourcePath.BuildAccess(ctx.Source);
var target = targetPath.BuildAccess(targetAccess);
return delegateMapping.Build(ctx.WithSource(source), target);
var mappingCtx = ctx.WithSource(source);
if (delegateMapping is NullDelegateExistingTargetMapping nullDelegateMapping)
{
var nullConditionalSource = sourcePath.BuildAccess(ctx.Source, nullConditional: true);
return nullDelegateMapping.Build(mappingCtx, target, nullConditionalSource);
}

return delegateMapping.Build(mappingCtx, target);
}
}
71 changes: 71 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumerableExistingTargetTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Tests.Mapping;

Expand Down Expand Up @@ -68,6 +69,76 @@ public void EnumerableToExistingNullableValueTypeCustomCollection()
);
}

[Fact]
public void NestedEnumerableToExistingReadOnlyCollectionInDisabledNullableContext()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"[MapProperty(new[] { nameof(Source.Nested), nameof(Nested.Items) }, nameof(Target.Items))] partial Target Map(Source source);",
"class Source { public Nested Nested { get; set; } }",
"class Nested { public IEnumerable<int> Items { get; set; } }",
"class Target { public readonly List<int> Items = []; }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.DisabledNullable)
.Should()
.HaveSingleMethodBody(
"""
if (source == null)
return default;
var target = new global::Target();
if (source.Nested?.Items != null && target.Items != null)
{
if (global::System.Linq.Enumerable.TryGetNonEnumeratedCount(source.Nested.Items, out var sourceCount))
{
target.Items.EnsureCapacity(sourceCount + target.Items.Count);
}
foreach (var item in source.Nested.Items)
{
target.Items.Add(item);
}
}
return target;
"""
);
}

[Fact]
public void NestedEnumerableToExistingReadOnlyCollectionWithNullableIntermediateMember()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"[MapProperty(new[] { nameof(Source.Nested), nameof(Nested.Items) }, nameof(Target.Items))] partial Target Map(Source source);",
"class Source { public Nested? Nested { get; set; } }",
"class Nested { public IEnumerable<int> Items { get; set; } = []; }",
"class Target { public readonly List<int> Items = []; }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(
DiagnosticDescriptors.NullableSourceTypeToNonNullableTargetType,
"Mapping the nullable source of type System.Collections.Generic.IEnumerable<int>? to target of type System.Collections.Generic.List<int> which is not nullable"
)
.HaveSingleMethodBody(
"""
var target = new global::Target();
if (source.Nested?.Items != null)
{
if (global::System.Linq.Enumerable.TryGetNonEnumeratedCount(source.Nested.Items, out var sourceCount))
{
target.Items.EnsureCapacity(sourceCount + target.Items.Count);
}
foreach (var item in source.Nested.Items)
{
target.Items.Add(item);
}
}
return target;
"""
);
}

[Fact]
public Task MapToExistingCollectionShouldWork()
{
Expand Down