Skip to content

Commit

Permalink
NullPropagate property path
Browse files Browse the repository at this point in the history
  • Loading branch information
chaowlert committed Feb 1, 2020
1 parent c316490 commit cae163e
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 43 deletions.
10 changes: 10 additions & 0 deletions src/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[*.cs]

# S3220: Method calls should not resolve ambiguously to overloads with "params"
dotnet_diagnostic.S3220.severity = suggestion

# RCS1238: Avoid nested ?: operators.
dotnet_diagnostic.RCS1238.severity = suggestion

# S3358: Ternary operators should not be nested
dotnet_diagnostic.S3358.severity = suggestion
19 changes: 19 additions & 0 deletions src/Mapster.Tests/WhenMappingWithPath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

Expand Down Expand Up @@ -42,6 +43,23 @@ public void MapPath()
dto2.Address.Number.ShouldBe(poco.Child.Address.Number + "test");
}

[TestMethod]
public void MapPath_AllowNull()
{
TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo();
TypeAdapterConfig<Poco, Dto>.NewConfig()
.Map(dest => dest.Location, src => src.Child.Address.Location);

var poco = new Poco
{
Id = Guid.NewGuid(),
Child = new ChildPoco()
};
var dto = poco.Adapt<Dto>();
dto.Id.ShouldBe(poco.Id);
dto.Location.ShouldBeNull();
}

[TestMethod]
public void MapPath_Condition()
{
Expand Down Expand Up @@ -156,6 +174,7 @@ public class Dto
{
public Guid Id { get; set; }
public Address Address { get; set; }
public string Location { get; set; }
}
}
}
5 changes: 3 additions & 2 deletions src/Mapster.sln
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A5580F9D-0F5F-4224-980F-7824536A627D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\LICENSE = ..\LICENSE
..\README.md = ..\README.md
EndProjectSection
Expand Down
4 changes: 2 additions & 2 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
var destMember = arg.MapType == MapType.MapToTarget || member.UseDestinationValue
? member.DestinationMember.GetExpression(destination)
: null;

var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember);
if (!member.UseDestinationValue)
{
Expand Down Expand Up @@ -192,7 +192,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
{
value = member.Getter.NullPropagate(value);
}
var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info, value);
var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, value);
lines.Add(bind);
}

Expand Down
8 changes: 4 additions & 4 deletions src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private static IEnumerable<Expression> GetArrayBounds(Expression source, Type de
{
yield return Expression.Constant(1);
}
yield return ExpressionEx.CreateCountExpression(source, true);
yield return ExpressionEx.CreateCountExpression(source, true)!;
}
else
{
Expand Down Expand Up @@ -66,8 +66,8 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
var method = typeof(Array).GetMethod("Copy", new[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) });
return Expression.Call(method, source, Expression.Constant(0), destination, Expression.Constant(0), ExpressionEx.CreateCountExpression(source, true));
}
else
return CreateArraySet(source, destination, arg);

return CreateArraySet(source, destination, arg);
}

protected override Expression CreateInlineExpression(Expression source, CompileArgument arg)
Expand Down Expand Up @@ -103,7 +103,7 @@ private Expression CreateArraySet(Expression source, Expression destination, Com

var method = typeof(Array).GetMethod("GetLength", new[] { typeof(int) });
block.AddRange(
vlenx.Select((vlen, i) =>
vlenx.Select((vlen, i) =>
Expression.Assign(
vlen,
Expression.Call(destination, method, Expression.Constant(i)))));
Expand Down
5 changes: 4 additions & 1 deletion src/Mapster/Mapster.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<RootNamespace>Mapster</RootNamespace>
<Version>5.0.0-PRE4</Version>
<LangVersion>8.0</LangVersion>
<Nullable Condition="'$(TargetFramework)' == 'netcoreapp3.0'">enable</Nullable>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
<Reference Include="Microsoft.CSharp" />
Expand All @@ -37,4 +37,7 @@
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
</Project>
20 changes: 18 additions & 2 deletions src/Mapster/Models/InvokerModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Mapster.Models
public class InvokerModel
{
public string DestinationMemberName { get; set; }
public LambdaExpression Invoker { get; set; }
public LambdaExpression? Invoker { get; set; }
public string? SourceMemberName { get; set; }
public LambdaExpression? Condition { get; set; }
public bool IsChildPath { get; set; }
Expand All @@ -24,10 +24,26 @@ public class InvokerModel
: Expression.Lambda(this.Condition.Apply(source), source),
Invoker = this.IsChildPath
? this.Invoker
: Expression.Lambda(this.Invoker?.Apply(source) ?? ExpressionEx.PropertyOrField(source, this.SourceMemberName!), source),
: Expression.Lambda(this.GetInvokingExpression(source), source),
SourceMemberName = this.SourceMemberName,
IsChildPath = true,
};
}

public Expression GetInvokingExpression(Expression exp, MapType mapType = MapType.Map)
{
if (this.IsChildPath)
return this.Invoker!.Body;
return this.SourceMemberName != null
? ExpressionEx.PropertyOrFieldPath(exp, this.SourceMemberName)
: this.Invoker!.Apply(mapType, exp);
}

public Expression? GetConditionExpression(Expression exp, MapType mapType = MapType.Map)
{
return this.IsChildPath
? this.Condition?.Body
: this.Condition?.Apply(mapType, exp);
}
}
}
28 changes: 11 additions & 17 deletions src/Mapster/Settings/ValueAccessingStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static class ValueAccessingStrategy
{
var config = arg.Settings;
var resolvers = config.Resolvers;
if (resolvers == null || resolvers.Count <= 0)
if (resolvers == null || resolvers.Count == 0)
return null;

var invokes = new List<Tuple<Expression, Expression>>();
Expand All @@ -38,21 +38,15 @@ public static class ValueAccessingStrategy
{
if (!destinationMember.Name.Equals(resolver.DestinationMemberName))
continue;
var invoke = resolver.Invoker == null
? ExpressionEx.PropertyOrField(source, resolver.SourceMemberName)
: resolver.IsChildPath
? resolver.Invoker.Body
: resolver.Invoker.Apply(arg.MapType, source);

if (resolver.Condition == null)
var invoke = resolver.GetInvokingExpression(source, arg.MapType);
var condition = resolver.GetConditionExpression(source, arg.MapType);
if (condition == null)
{
getter = invoke;
break;
}

var condition = resolver.IsChildPath
? resolver.Condition.Body
: resolver.Condition.Apply(arg.MapType, source);
invokes.Add(Tuple.Create(condition, invoke));
}

Expand Down Expand Up @@ -208,7 +202,7 @@ private static IEnumerable<string> GetDeepUnflattening(IMemberModel destinationM
{
var config = arg.Settings;
var resolvers = config.Resolvers;
if (resolvers == null || resolvers.Count <= 0)
if (resolvers == null || resolvers.Count == 0)
return null;
var dictType = source.Type.GetDictionaryType();
if (dictType == null)
Expand All @@ -217,24 +211,24 @@ private static IEnumerable<string> GetDeepUnflattening(IMemberModel destinationM
var method = typeof(MapsterHelper).GetMethods().First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault)).MakeGenericMethod(args);

Expression? getter = null;
LambdaExpression? lastCondition = null;
Expression? lastCondition = null;
foreach (var resolver in resolvers)
{
if (!destinationMember.Name.Equals(resolver.DestinationMemberName))
continue;

Expression invoke = resolver.Invoker == null
? Expression.Call(method, source.To(dictType), Expression.Constant(resolver.SourceMemberName))
: resolver.Invoker.Apply(arg.MapType, source);
: resolver.GetInvokingExpression(source, arg.MapType);
getter = lastCondition != null
? Expression.Condition(lastCondition.Apply(arg.MapType, source), getter, invoke)
? Expression.Condition(lastCondition, getter, invoke)
: invoke;
lastCondition = resolver.Condition;
if (resolver.Condition == null)
lastCondition = resolver.GetConditionExpression(source, arg.MapType);
if (lastCondition == null)
break;
}
if (lastCondition != null)
getter = Expression.Condition(lastCondition.Apply(arg.MapType, source), getter!, getter!.Type.CreateDefault());
getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault());
return getter;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/TypeAdapterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Mapster.Adapters;
using Mapster.Utils;
using System.Runtime.CompilerServices;

namespace Mapster
{
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Utils/CoreExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static string ToPascalCase(this string name)
return NameMatchingStrategy.PascalCase(name);
}

#if !NETCOREAPP3_0
#if !NETCOREAPP
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
return new HashSet<T>(source);
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Utils/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Mapster.Utils
{
public static class Enum<TEnum> where TEnum : struct
public static class Enum<TEnum> where TEnum : struct
{
private static readonly Dictionary<string, TEnum> _lookup = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(x => x.ToString().ToUpperInvariant(), y => y);
private static readonly Dictionary<TEnum, string> _reverseLookup = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(x => x, y => y.ToString());
Expand Down
29 changes: 20 additions & 9 deletions src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ public static Expression Assign(Expression left, Expression right)
return Expression.Assign(left, middle);
}

public static Expression PropertyOrField(Expression expr, string path)
public static Expression PropertyOrFieldPath(Expression expr, string path)
{
var props = path.Split('.');
return props.Aggregate(expr, Expression.PropertyOrField);
return expr.NullPropagate(props);
}

private static bool IsReferenceAssignableFrom(this Type destType, Type srcType)
Expand All @@ -46,19 +46,14 @@ public static Expression Apply(this LambdaExpression lambda, MapType mapType, pa
return lambda.Apply(mapType != MapType.Projection, exps);
}

public static Expression Apply(this LambdaExpression lambda, ParameterExpression p1, ParameterExpression? p2)
public static Expression Apply(this LambdaExpression lambda, ParameterExpression p1, ParameterExpression? p2 = null)
{
if (p2 == null)
return lambda.Apply(false, p1);
else
return lambda.Apply(false, p1, p2);
}

public static Expression Apply(this LambdaExpression lambda, params ParameterExpression[] exps)
{
return lambda.Apply(false, exps.Cast<Expression>().ToArray());
}

private static Expression Apply(this LambdaExpression lambda, bool allowInvoke, params Expression[] exps)
{
var replacer = new ParameterExpressionReplacer(lambda.Parameters, exps);
Expand Down Expand Up @@ -243,7 +238,7 @@ public static bool CanBeNull(this Expression exp)
{
if (!exp.Type.CanBeNull())
return false;

var visitor = new NullableExpressionVisitor();
visitor.Visit(exp);
return visitor.CanBeNull.GetValueOrDefault();
Expand Down Expand Up @@ -287,5 +282,21 @@ public static Expression NullPropagate(this Expression exp, Expression value)
value.Type.CreateDefault(),
value);
}

public static Expression NullPropagate(this Expression exp, string[] props, int i = 0)
{
if (props.Length <= i)
return exp;
var head = Expression.PropertyOrField(exp, props[i]);
var tail = NullPropagate(head, props, i + 1);
if (!exp.CanBeNull())
return tail;

var compareNull = Expression.Equal(exp, Expression.Constant(null, exp.Type));
return Expression.Condition(
compareNull,
tail.Type.CreateDefault(),
tail);
}
}
}
4 changes: 1 addition & 3 deletions src/Mapster/Utils/NullableExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
Expand Down

0 comments on commit cae163e

Please sign in to comment.