Skip to content

Commit

Permalink
fix: add correct type for default null-fallback statements when using…
Browse files Browse the repository at this point in the history
… nullable value types
  • Loading branch information
rickykaare authored Sep 12, 2023
1 parent e073f74 commit 4fa4163
Show file tree
Hide file tree
Showing 13 changed files with 80 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,15 @@ public ExpressionSyntax Build(TypeMappingBuildContext ctx)
var mapping = _delegateMapping.Build(ctx.WithSource(nullConditionalSourceAccess));
return _nullFallback == NullFallbackValue.Default && _targetType.IsNullable()
? mapping
: Coalesce(mapping, NullSubstitute(_delegateMapping.TargetType, nameofSourceAccess, _nullFallback));
: Coalesce(mapping, NullSubstitute(_targetType, nameofSourceAccess, _nullFallback));
}

var notNullCondition = _useNullConditionalAccess
? IsNotNull(SourcePath.BuildAccess(ctx.Source, nullConditional: true, skipTrailingNonNullable: true))
: SourcePath.BuildNonNullConditionWithoutConditionalAccess(ctx.Source)!;
var sourceMemberAccess = SourcePath.BuildAccess(ctx.Source, true);
ctx = ctx.WithSource(sourceMemberAccess);
return Conditional(
notNullCondition,
_delegateMapping.Build(ctx),
NullSubstitute(_delegateMapping.TargetType, sourceMemberAccess, _nullFallback)
);
return Conditional(notNullCondition, _delegateMapping.Build(ctx), NullSubstitute(_targetType, sourceMemberAccess, _nullFallback));
}

protected bool Equals(NullMemberMapping other) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx)

return Conditional(
IsNull(ctx.Source),
NullSubstitute(TargetType.NonNullable(), ctx.Source, _nullFallbackValue),
NullSubstitute(TargetType, ctx.Source, _nullFallbackValue),
_delegateMapping.Build(ctx.WithSource(sourceValue))
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private IEnumerable<StatementSyntax> AddPreNullHandling(TypeMappingBuildContext
// call mapping only if source is not null.
// if (source == null)
// return <null-substitute>;
var fallbackExpression = NullSubstitute(TargetType.NonNullable(), ctx.Source, _nullFallbackValue);
var fallbackExpression = NullSubstitute(TargetType, ctx.Source, _nullFallbackValue);
var ifExpression = ctx.SyntaxFactory.IfNullReturnOrThrow(ctx.Source, fallbackExpression);
return body.Prepend(ifExpression);
}
Expand Down
1 change: 1 addition & 0 deletions src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Null.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static ExpressionSyntax NullSubstitute(ITypeSymbol t, ExpressionSyntax ar
{
return nullFallbackValue switch
{
NullFallbackValue.Default when t.IsNullableValueType() => DefaultExpression(FullyQualifiedIdentifier(t)),
NullFallbackValue.Default => DefaultLiteral(),
NullFallbackValue.EmptyString => StringLiteral(string.Empty),
NullFallbackValue.CreateInstance => CreateInstance(t),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
SELECT "o"."CtorValue", "o"."IntValue", "o"."IntInitOnlyValue", "o"."RequiredValue", "o"."StringValue", "o"."RenamedStringValue", "i"."IdValue", CASE
WHEN "i0"."IdValue" IS NOT NULL THEN "i0"."IdValue"
ELSE 0
END, CASE
SELECT "o"."CtorValue", "o"."IntValue", "o"."IntInitOnlyValue", "o"."RequiredValue", "o"."StringValue", "o"."RenamedStringValue", "i"."IdValue", "i0"."IdValue", CASE
WHEN "t"."IntValue" IS NOT NULL THEN "t"."IntValue"
ELSE 0
END, "t"."IntValue" IS NOT NULL, "t"."IntValue", "t0"."IntValue" IS NOT NULL, "t0"."IntValue", COALESCE("o"."StringNullableTargetNotNullable", ''), "o0"."Id", "o0"."CtorValue", "o0"."DateTimeValueTargetDateOnly", "o0"."DateTimeValueTargetTimeOnly", "o0"."EnumName", "o0"."EnumRawValue", "o0"."EnumReverseStringValue", "o0"."EnumStringValue", "o0"."EnumValue", "o0"."FlatteningIdValue", "o0"."IgnoredIntValue", "o0"."IgnoredStringValue", "o0"."IntInitOnlyValue", "o0"."IntValue", "o0"."ManuallyMapped", "o0"."NestedNullableIntValue", "o0"."NestedNullableTargetNotNullableIntValue", "o0"."NullableFlatteningIdValue", "o0"."NullableUnflatteningIdValue", "o0"."RecursiveObjectId", "o0"."RenamedStringValue", "o0"."RequiredValue", "o0"."StringNullableTargetNotNullable", "o0"."StringValue", "o0"."SubObjectSubIntValue", "o0"."UnflatteningIdValue", "o"."Id", "i0"."IdValue", "i1"."SubIntValue", "t1"."IntValue", "t1"."TestObjectProjectionId", "t2"."IntValue", CAST("o"."EnumValue" AS INTEGER), CAST("o"."EnumName" AS INTEGER), CAST("o"."EnumRawValue" AS INTEGER), "o"."EnumStringValue", "o"."EnumReverseStringValue", "i1"."SubIntValue" IS NOT NULL, "i1"."BaseIntValue", "o"."DateTimeValueTargetDateOnly", "o"."DateTimeValueTargetTimeOnly", "o"."ManuallyMapped", "t3"."Id", "t3"."OtherValue", "t3"."TestObjectProjectionId", "t3"."Value"
END, "t"."IntValue" IS NOT NULL, "t"."IntValue", "t0"."IntValue" IS NOT NULL, "t0"."IntValue", COALESCE("o"."StringNullableTargetNotNullable", ''), "o0"."Id", "o0"."CtorValue", "o0"."DateTimeValueTargetDateOnly", "o0"."DateTimeValueTargetTimeOnly", "o0"."EnumName", "o0"."EnumRawValue", "o0"."EnumReverseStringValue", "o0"."EnumStringValue", "o0"."EnumValue", "o0"."FlatteningIdValue", "o0"."IgnoredIntValue", "o0"."IgnoredStringValue", "o0"."IntInitOnlyValue", "o0"."IntValue", "o0"."ManuallyMapped", "o0"."NestedNullableIntValue", "o0"."NestedNullableTargetNotNullableIntValue", "o0"."NullableFlatteningIdValue", "o0"."NullableUnflatteningIdValue", "o0"."RecursiveObjectId", "o0"."RenamedStringValue", "o0"."RequiredValue", "o0"."StringNullableTargetNotNullable", "o0"."StringValue", "o0"."SubObjectSubIntValue", "o0"."UnflatteningIdValue", "o"."Id", "i1"."SubIntValue", "t1"."IntValue", "t1"."TestObjectProjectionId", "t2"."IntValue", CAST("o"."EnumValue" AS INTEGER), CAST("o"."EnumName" AS INTEGER), CAST("o"."EnumRawValue" AS INTEGER), "o"."EnumStringValue", "o"."EnumReverseStringValue", "i1"."SubIntValue" IS NOT NULL, "i1"."BaseIntValue", "o"."DateTimeValueTargetDateOnly", "o"."DateTimeValueTargetTimeOnly", "o"."ManuallyMapped", "t3"."Id", "t3"."OtherValue", "t3"."TestObjectProjectionId", "t3"."Value"
FROM "Objects" AS "o"
INNER JOIN "IdObject" AS "i" ON "o"."FlatteningIdValue" = "i"."IdValue"
LEFT JOIN "IdObject" AS "i0" ON "o"."NullableFlatteningIdValue" = "i0"."IdValue"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
StringValue: ,
RenamedStringValue2: ,
FlatteningIdValue: 1,
NullableFlatteningIdValue: 0,
NestedNullableTargetNotNullable: {},
StringNullableTargetNotNullable: ,
SourceTargetSameObjectType: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static partial class ProjectionMapper
StringValue = x.StringValue,
RenamedStringValue2 = x.RenamedStringValue,
FlatteningIdValue = x.Flattening.IdValue,
NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default,
NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default(int?),
NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default,
NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static partial int DirectInt(int value)

public static partial int? DirectIntNullable(int? value)
{
return value == null ? default : value.Value;
return value == null ? default(int?) : value.Value;
}

public static partial long ImplicitCastInt(int value)
Expand Down
5 changes: 4 additions & 1 deletion test/Riok.Mapperly.Tests/Mapping/EnumTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ public void NullableEnumToOtherNullableEnumShouldCast()
{
var source = TestSourceBuilder.Mapping("E1?", "E2?", "enum E1 {A, B, C}", "enum E2 {A, B, C}");

TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return source == null ? default : (global::E2)source.Value;");
TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody("return source == null ? default(global::E2? ) : (global::E2)source.Value;");
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion test/Riok.Mapperly.Tests/Mapping/NullableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void NullablePrimitiveToOtherNullablePrimitiveShouldWork()
{
var source = TestSourceBuilder.Mapping("decimal?", "int?");

TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody(@"return source == null ? default : (int)source.Value;");
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody(@"return source == null ? default(int? ) : (int)source.Value;");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,4 +483,68 @@ public void CanResolveToClassConstructorWithMapPropertyAttribute()
"""
);
}

[Fact]
public void RecordToRecordNullableToNullableEnum()
{
var source = TestSourceBuilder.CSharp(
"""
using Riok.Mapperly.Abstractions;

enum SourceEnum { Value1, Value2 }
enum TargetEnum { Value1, Value2 }

record SourceRecord(SourceEnum? EnumValue);
record TargetRecord(TargetEnum? EnumValue);

[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
static partial class Mapper
{
public static partial TargetRecord Map(this SourceRecord record);
}
"""
);

TestHelper
.GenerateMapper(source)
.Should()
.HaveMapMethodBody(
"""
var target = new global::TargetRecord(record.EnumValue != null ? MapToTargetEnum(record.EnumValue.Value) : default(global::TargetEnum? ));
return target;
"""
);
}

[Fact]
public void RecordToRecordNullableToNullableStruct()
{
var source = TestSourceBuilder.CSharp(
"""
using Riok.Mapperly.Abstractions;

struct SourceStruct { public int Value { get; set; } }
struct TargetStruct { public int Value { get; set; } }

record SourceRecord(SourceStruct? StructValue);
record TargetRecord(TargetStruct? StructValue);

[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
static partial class Mapper
{
public static partial TargetRecord Map(this SourceRecord record);
}
"""
);

TestHelper
.GenerateMapper(source)
.Should()
.HaveMapMethodBody(
"""
var target = new global::TargetRecord(record.StructValue != null ? MapToTargetStruct(record.StructValue.Value) : default(global::TargetStruct? ));
return target;
"""
);
}
}
2 changes: 1 addition & 1 deletion test/Riok.Mapperly.Tests/Mapping/ParseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void ParseableBuiltInClass()
public void ParseableBuiltNullableInClass()
{
var source = TestSourceBuilder.Mapping("string?", "int?");
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return source == null ? default : int.Parse(source);");
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return source == null ? default(int? ) : int.Parse(source);");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ string x when targetType.IsAssignableFrom(typeof(int)) => MapStringToInt(x),

private partial int? MapStringToInt(string? source)
{
return source == null ? default : int.Parse(source);
return source == null ? default(int?) : int.Parse(source);
}

private partial int? MapIntToInt(int source)
Expand Down

0 comments on commit 4fa4163

Please sign in to comment.