Skip to content

Commit 103ef01

Browse files
committed
Improved wrapper check for existing parameterized ctor.
1 parent b5162fa commit 103ef01

4 files changed

Lines changed: 45 additions & 5 deletions

File tree

DomainModeling.Generator/IdentityGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
297297
existingComponents |= IdTypeComponents.UnsettableValue.If(members.Any(member => member.Name == "Value" && member is not IFieldSymbol && member is not IPropertySymbol { SetMethod: not null }));
298298

299299
existingComponents |= IdTypeComponents.Constructor.If(type.InstanceConstructors.Any(ctor =>
300-
ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.Equals(underlyingType, SymbolEqualityComparer.Default)));
300+
ctor.Parameters.Length >= 1 && (ctor.Parameters.Length is 1 || ctor.Parameters[1].HasExplicitDefaultValue) && // Callable with exactly 1 parameter
301+
ctor.Parameters[0].Type.Equals(underlyingType, SymbolEqualityComparer.Default)));
301302

302303
// Records override this, but our implementation is superior
303304
existingComponents |= IdTypeComponents.ToStringOverride.If(members.Any(member =>

DomainModeling.Generator/WrapperValueObjectGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
193193
existingComponents |= WrapperValueObjectTypeComponents.UnsettableValue.If(members.Any(member => member.Name == "Value" && member is not IFieldSymbol && member is not IPropertySymbol { SetMethod: not null }));
194194

195195
existingComponents |= WrapperValueObjectTypeComponents.Constructor.If(type.InstanceConstructors.Any(ctor =>
196-
ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.Equals(underlyingType, SymbolEqualityComparer.Default)));
196+
ctor.Parameters.Length >= 1 && (ctor.Parameters.Length is 1 || ctor.Parameters[1].HasExplicitDefaultValue) && // Callable with exactly 1 parameter
197+
ctor.Parameters[0].Type.Equals(underlyingType, SymbolEqualityComparer.Default)));
197198

198199
existingComponents |= WrapperValueObjectTypeComponents.NullableConstructor.If(underlyingType.IsValueType && type.InstanceConstructors.Any(ctor =>
199200
ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.IsNullableOf(underlyingType)));

DomainModeling.Tests/IdentityTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,16 @@ public void ParsabilityAndFormattability_InAllScenarios_ShouldBeGeneratedAccordi
899899
Assert.DoesNotContain(interfaces, interf => interf.Name == "IUtf8SpanFormattable");
900900
Assert.DoesNotContain(interfaces, interf => interf.Name == "IUtf8SpanParsable`1");
901901
}
902+
903+
/// <summary>
904+
/// A multi-param ctor with the 2nd and further parameters optional should prevent the single-param ctor from being generated.
905+
/// </summary>
906+
[Fact]
907+
public void Construct_WithManualConstructorWithSecondParamOptional_ShouldUseThat()
908+
{
909+
var result = new ManualCtorIntId(1);
910+
Assert.Equal(Int32.MinValue, result.Value); // Hand-written ctor should have been used
911+
}
902912
}
903913

904914
// Use a namespace, since our source generators dislike nested types
@@ -999,6 +1009,15 @@ public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnly
9991009
throw new Exception("Serialization should have delegated to the wrapped value.");
10001010
}
10011011
}
1012+
[IdentityValueObject<int>]
1013+
internal partial struct ManualCtorIntId
1014+
{
1015+
public ManualCtorIntId(int value, string? paramName = null)
1016+
{
1017+
this.Value = value is Int32.MinValue ? value : Int32.MinValue;
1018+
_ = paramName;
1019+
}
1020+
}
10021021

10031022
/// <summary>
10041023
/// Should merely compile.

DomainModeling.Tests/WrapperValueObjectTests.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public void EqualityOperator_WithNullables_ShouldReturnExpectedResult()
167167
#pragma warning disable IDE0004 // Deliberate casts to test specific operators
168168
#pragma warning disable CS8073 // Deliberate casts to test specific operators
169169
Assert.True((StringValue?)null == (StringValue?)null);
170-
Assert.True((DecimalValue?)null == (DecimalValue?) null);
170+
Assert.True((DecimalValue?)null == (DecimalValue?)null);
171171

172172
Assert.False((StringValue?)null == (StringValue?)"");
173173
Assert.False((DecimalValue?)null == (DecimalValue?)0);
@@ -382,7 +382,7 @@ public void CastToCoreType_Regularly_ShouldReturnExpectedResult(int? value, int?
382382
[InlineData(1, 1)]
383383
public void CastToNullableCoreType_Regularly_ShouldReturnExpectedResult(int? value, int? expectedResult)
384384
{
385-
var intInstance = value is null ? (NestedIntValue?)null: new NestedIntValue(new IntValue(value.Value));
385+
var intInstance = value is null ? (NestedIntValue?)null : new NestedIntValue(new IntValue(value.Value));
386386
Assert.Equal(expectedResult, (int?)intInstance);
387387

388388
var decimalInstance = value is null ? null : new NestedDecimalValue(new DecimalValue(value.Value));
@@ -601,7 +601,7 @@ public void Deserialize_FromCoreType_ShouldReturnExpectedResult(int value)
601601
{
602602
Assert.IsType<FormatAndParseTestingIntWrapper>(Deserialize<FormatAndParseTestingIntWrapper, int>(value));
603603
Assert.Equal(value, Deserialize<FormatAndParseTestingIntWrapper, int>(value).Value.Value);
604-
604+
605605
Assert.IsType<FormatAndParseTestingStringWrapper>(Deserialize<FormatAndParseTestingStringWrapper, string>(value.ToString()));
606606
Assert.Equal(value.ToString(), Deserialize<FormatAndParseTestingStringWrapper, string>(value.ToString()).Value.Value.Value.Value);
607607
}
@@ -841,6 +841,16 @@ public void ParsabilityAndFormattability_InAllScenarios_ShouldBeGeneratedAccordi
841841
Assert.DoesNotContain(interfaces, interf => interf.Name == "IUtf8SpanFormattable");
842842
Assert.DoesNotContain(interfaces, interf => interf.Name == "IUtf8SpanParsable`1");
843843
}
844+
845+
/// <summary>
846+
/// A multi-param ctor with the 2nd and further parameters optional should prevent the single-param ctor from being generated.
847+
/// </summary>
848+
[Fact]
849+
public void Construct_WithManualConstructorWithSecondParamOptional_ShouldUseThat()
850+
{
851+
var result = new ManualCtorIntWrapper(1);
852+
Assert.Equal(Int32.MinValue, result.Value); // Hand-written ctor should have been used
853+
}
844854
}
845855

846856
// Use a namespace, since our source generators dislike nested types
@@ -1045,6 +1055,15 @@ public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnly
10451055
throw new Exception("Serialization should have delegated to the wrapped value.");
10461056
}
10471057
}
1058+
[WrapperValueObject<int>]
1059+
internal partial struct ManualCtorIntWrapper
1060+
{
1061+
public ManualCtorIntWrapper(int value, string? paramName = null)
1062+
{
1063+
this.Value = value is Int32.MinValue ? value : Int32.MinValue;
1064+
_ = paramName;
1065+
}
1066+
}
10481067

10491068
/// <summary>
10501069
/// Should merely compile.

0 commit comments

Comments
 (0)