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
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Properties related to build/pack -->
<IsPackable>false</IsPackable>
<Version>10.0.7-pre01</Version>
<Version>10.0.7-pre02</Version>
<MapsterPluginsTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterPluginsTFMs>
<MapsterTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterTFMs>
<MapsterEFCoreTFMs>net10.0;net9.0;net8.0</MapsterEFCoreTFMs>
Expand Down
113 changes: 113 additions & 0 deletions src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System.Threading.Tasks;

namespace Mapster.Tests;

[TestClass]
public class WhenPropertyNullablePropagationRegression
{
/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/858
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task NotNullableStructMapToNotNullableCorrect()
{
TypeAdapterConfig<Foo858, Bar858>
.NewConfig()
.Map(dest => dest.Amount, src => src.Amount)
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);


Foo858 foo = new()
{
Amount = new(1, Currency858.Usd),
Inner = new()
{
Amount = new(10, Currency858.Eur),
Int = 100,
}
};

// Act
var bar = foo.Adapt<Bar858>();
// Assert
bar.InnerAmount.Amount.ShouldBe(10m);
}

[TestMethod]
public async Task NotNullableStructMapToNullableCorrect()
{
TypeAdapterConfig<Foo858, Bar858Nullable>
.NewConfig()
.Map(dest => dest.Amount, src => src.Amount)
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);


Foo858 foo = new()
{
Amount = new(1, Currency858.Usd),
Inner = new()
{
Amount = new(10, Currency858.Eur),
Int = 100,
}
};

// Act
var bar = foo.Adapt<Bar858Nullable>();
// Assert
bar.InnerAmount?.Amount.ShouldBe(10m);
}

}

#region TestClasses
public enum Currency858
{
Eur,
Usd,
Ron
}

file class Foo858
{
public required Money858 Amount { get; set; }
public required FooInner858 Inner { get; set; }
}

file class FooInner858
{
public required Money858 Amount { get; set; }
public int Int { get; set; }
}

file class Bar858
{
public Money858 Amount { get; set; }
public Money858 InnerAmount { get; set; }

}

file class Bar858Nullable
{
public Money858? Amount { get; set; }
public Money858? InnerAmount { get; set; }

}

public struct Money858
{
public decimal? Amount { get; set; }

public Currency858 Currency { get; set; } = Currency858.Ron;

public Money858(decimal? amount, Currency858 currency = Currency858.Eur)
{
Amount = amount;
Currency = currency;
}
}

#endregion TestClasses
4 changes: 2 additions & 2 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

#region Build the Adapter Model

protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool ctorMapping = false, ClassModel recordRestorMemberModel = null)

Check warning on line 18 in src/Mapster/Adapters/BaseClassAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var destinationMembers = classModel.Members;
var unmappedDestinationMembers = new List<string>();
Expand Down Expand Up @@ -132,7 +132,7 @@
{
propertyModel.Getter = arg.MapType == MapType.Projection
? getter
: getter.ApplyNullPropagation();
: getter.ApplyPropertyNullPropagation(propertyModel);
properties.Add(propertyModel);
}
else
Expand Down Expand Up @@ -213,14 +213,14 @@
&& ignore.Condition == null;
}

protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination, ClassModel recordRestorParamModel = null)

Check warning on line 216 in src/Mapster/Adapters/BaseClassAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var members = classConverter.Members;

var arguments = new List<Expression>();
foreach (var member in members)
{
arg.Context.NullChecks.UnionWith(members.Select(x=>(x.Getter,arg)));
arg.Context.NullChecks.UnionWith(members.Where(x=>x.Getter != null).Select(x=>(x.Getter,arg)));
var parameterInfo = (ParameterInfo)member.DestinationMember.Info!;
var defaultConst = parameterInfo.IsOptional
? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type)
Expand Down
30 changes: 22 additions & 8 deletions src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,25 +407,36 @@ public static Expression NullableEnumExtractor(this Expression param)

return param;
}
public static Expression ApplyNullPropagation(this Expression getter)
public static Expression ApplyPropertyNullPropagation(this Expression getter, MemberMapping property)
{
var current = getter;
var result = getter;
Expression? condition = null;

while (current.NodeType == ExpressionType.MemberAccess)
{
var memEx = (MemberExpression) current;
var expr = memEx.Expression;
if (expr == null)
break;
if (expr.NodeType == ExpressionType.Parameter)
return result;
if (expr.NodeType == ExpressionType.Parameter && condition != null)
{
if (property.DestinationMember.Type.CanBeNull() && !getter.CanBeNull())
{
var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type));
return Expression.Condition(condition, transform, transform.Type.CreateDefault());
}
else
return Expression.Condition(condition, getter, getter.Type.CreateDefault());
}

if (expr.CanBeNull())
{
var compareNull = Expression.Equal(expr, Expression.Constant(null, expr.Type));
if (!result.Type.CanBeNull())
result = Expression.Convert(result, typeof(Nullable<>).MakeGenericType(result.Type));
result = Expression.Condition(compareNull, result.Type.CreateDefault(), result);
var compareNull = Expression.NotEqual(expr, Expression.Constant(null, expr.Type));
if (condition == null)
condition = compareNull;
else
condition = Expression.AndAlso(compareNull, condition);
}

current = expr;
Expand All @@ -436,11 +447,14 @@ public static Expression ApplyNullPropagation(this Expression getter)

public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg)
{
if (getter == null)
return adapt;

Expression? condition = null;
var current = getter;
var checks = arg.Context.NullChecks
.Where(x=> !object.ReferenceEquals(x.arg,arg))
.Select(x=>x.param.Type);
.Select(x=>x.param?.Type);

while (current != null)
{
Expand Down
Loading