Skip to content

Commit bbc972d

Browse files
committed
Struct-ValueObjects with struct-key-member need 2 implicit conversions.
1 parent 040f136 commit bbc972d

File tree

4 files changed

+248
-27
lines changed

4 files changed

+248
-27
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>5.0.0</VersionPrefix>
5+
<VersionPrefix>5.0.1</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,22 +211,27 @@ private void GenerateImplicitConversionToKey(EqualityInstanceMemberInfo keyMembe
211211
/// <param name=""obj"">Object to covert.</param>
212212
/// <returns>The <see cref=""{keyMember.Name}""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
213213
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
214-
public static implicit operator {keyMember.TypeFullyQualifiedNullable}({_state.TypeFullyQualifiedNullAnnotated} obj)
215-
{{");
214+
public static implicit operator {keyMember.TypeFullyQualifiedNullable}({(_state.IsReferenceType ? _state.TypeFullyQualifiedNullAnnotated : _state.TypeFullyQualifiedNullable)} obj)
215+
{{
216+
return obj?.{keyMember.Name};
217+
}}");
216218

217-
if (_state.IsReferenceType)
218-
{
219-
_sb.Append($@"
220-
return obj is null ? null : obj.{keyMember.Name};");
221-
}
222-
else
223-
{
224-
_sb.Append($@"
225-
return obj.{keyMember.Name};");
226-
}
219+
if (_state.IsReferenceType || keyMember.IsReferenceType)
220+
return;
227221

228-
_sb.Append(@"
229-
}");
222+
// if value object and key member are structs
223+
224+
_sb.Append($@"
225+
226+
/// <summary>
227+
/// Implicit conversion to the type <see cref=""{keyMember.TypeMinimallyQualified}""/>.
228+
/// </summary>
229+
/// <param name=""obj"">Object to covert.</param>
230+
/// <returns>The <see cref=""{keyMember.Name}""/> of provided <paramref name=""obj""/>.</returns>
231+
public static implicit operator {keyMember.TypeFullyQualified}({_state.TypeFullyQualified} obj)
232+
{{
233+
return obj.{keyMember.Name};
234+
}}");
230235
}
231236

232237
private void GenerateExplicitConversionToKey(EqualityInstanceMemberInfo keyMemberInfo)

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public sealed class ValueObjectSourceGeneratorState :
1111
private readonly INamedTypeSymbol _type;
1212

1313
public string TypeFullyQualified { get; }
14+
public string TypeFullyQualifiedNullable { get; }
1415
public string TypeFullyQualifiedNullAnnotated { get; }
1516
public string TypeMinimallyQualified { get; }
1617

@@ -42,7 +43,8 @@ public ValueObjectSourceGeneratorState(
4243

4344
Namespace = type.ContainingNamespace?.IsGlobalNamespace == true ? null : type.ContainingNamespace?.ToString();
4445
TypeFullyQualified = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
45-
TypeFullyQualifiedNullAnnotated = type.IsReferenceType ? $"{TypeFullyQualified}?" : TypeFullyQualified;
46+
TypeFullyQualifiedNullable = $"{TypeFullyQualified}?";
47+
TypeFullyQualifiedNullAnnotated = type.IsReferenceType ? TypeFullyQualifiedNullable : TypeFullyQualified;
4648
TypeMinimallyQualified = type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
4749

4850
AssignableInstanceFieldsAndProperties = _type.GetAssignableFieldsAndPropertiesAndCheckForReadOnly(true, cancellationToken).ToList();

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs

Lines changed: 225 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,9 +1056,9 @@ internal static void ModuleInit()
10561056
/// <param name=""obj"">Object to covert.</param>
10571057
/// <returns>The <see cref=""ReferenceField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
10581058
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
1059-
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject obj)
1059+
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
10601060
{
1061-
return obj.ReferenceField;
1061+
return obj?.ReferenceField;
10621062
}
10631063
10641064
/// <summary>
@@ -1151,6 +1151,220 @@ public int CompareTo(global::Thinktecture.Tests.TestValueObject obj)
11511151
");
11521152
}
11531153

1154+
[Fact]
1155+
public void Should_generate_struct_with_int_key_member()
1156+
{
1157+
var source = @"
1158+
using System;
1159+
using Thinktecture;
1160+
1161+
namespace Thinktecture.Tests
1162+
{
1163+
[ValueObject]
1164+
public readonly partial struct TestValueObject
1165+
{
1166+
public readonly int StructField;
1167+
}
1168+
}
1169+
";
1170+
var output = GetGeneratedOutput<ValueObjectSourceGenerator>(source, typeof(ValueObjectAttribute).Assembly);
1171+
AssertOutput(output, _GENERATED_HEADER + @"
1172+
namespace Thinktecture.Tests
1173+
{
1174+
public class TestValueObject_ValueObjectTypeConverter : global::Thinktecture.ValueObjectTypeConverter<global::Thinktecture.Tests.TestValueObject, int>
1175+
{
1176+
/// <inheritdoc />
1177+
protected override global::Thinktecture.Tests.TestValueObject ConvertFrom(int structField)
1178+
{
1179+
return global::Thinktecture.Tests.TestValueObject.Create(structField);
1180+
}
1181+
1182+
/// <inheritdoc />
1183+
protected override int GetKeyValue(global::Thinktecture.Tests.TestValueObject obj)
1184+
{
1185+
return (int) obj;
1186+
}
1187+
}
1188+
1189+
[global::Thinktecture.Internal.ValueObjectConstructor(nameof(StructField))]
1190+
[global::Thinktecture.Internal.KeyedValueObject]
1191+
[global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.Tests.TestValueObject_ValueObjectTypeConverter))]
1192+
partial struct TestValueObject : global::System.IEquatable<global::Thinktecture.Tests.TestValueObject>, global::System.IFormattable, global::System.IComparable, global::System.IComparable<global::Thinktecture.Tests.TestValueObject>
1193+
{
1194+
[global::System.Runtime.CompilerServices.ModuleInitializer]
1195+
internal static void ModuleInit()
1196+
{
1197+
var convertFromKey = new global::System.Func<int, global::Thinktecture.Tests.TestValueObject>(global::Thinktecture.Tests.TestValueObject.Create);
1198+
global::System.Linq.Expressions.Expression<global::System.Func<int, global::Thinktecture.Tests.TestValueObject>> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField);
1199+
global::System.Linq.Expressions.Expression<global::System.Func<int, global::Thinktecture.Tests.TestValueObject>> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField);
1200+
1201+
var convertToKey = new global::System.Func<global::Thinktecture.Tests.TestValueObject, int>(static item => item.StructField);
1202+
global::System.Linq.Expressions.Expression<global::System.Func<global::Thinktecture.Tests.TestValueObject, int>> convertToKeyExpression = static obj => obj.StructField;
1203+
1204+
var tryCreate = new global::Thinktecture.Internal.Validate<global::Thinktecture.Tests.TestValueObject, int>(global::Thinktecture.Tests.TestValueObject.TryCreate);
1205+
1206+
var type = typeof(global::Thinktecture.Tests.TestValueObject);
1207+
var metadata = new global::Thinktecture.Internal.ValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression, tryCreate);
1208+
1209+
global::Thinktecture.Internal.ValueObjectMetadataLookup.AddMetadata(type, metadata);
1210+
}
1211+
1212+
private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject);
1213+
1214+
public static readonly global::Thinktecture.Tests.TestValueObject Empty = default;
1215+
1216+
public static global::Thinktecture.Tests.TestValueObject Create(int structField)
1217+
{
1218+
var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success;
1219+
ValidateFactoryArguments(ref validationResult, ref structField);
1220+
1221+
if(validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success)
1222+
throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? ""Validation failed."");
1223+
1224+
var obj = new global::Thinktecture.Tests.TestValueObject(structField);
1225+
obj.FactoryPostInit();
1226+
1227+
return obj;
1228+
}
1229+
1230+
public static global::System.ComponentModel.DataAnnotations.ValidationResult? TryCreate(
1231+
int structField,
1232+
[global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestValueObject obj)
1233+
{
1234+
var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success;
1235+
ValidateFactoryArguments(ref validationResult, ref structField);
1236+
1237+
if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success)
1238+
{
1239+
obj = new global::Thinktecture.Tests.TestValueObject(structField);
1240+
obj.FactoryPostInit();
1241+
}
1242+
else
1243+
{
1244+
obj = default;
1245+
}
1246+
1247+
return validationResult;
1248+
}
1249+
1250+
static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField);
1251+
1252+
partial void FactoryPostInit();
1253+
1254+
/// <summary>
1255+
/// Implicit conversion to the type <see cref=""int""/>.
1256+
/// </summary>
1257+
/// <param name=""obj"">Object to covert.</param>
1258+
/// <returns>The <see cref=""StructField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
1259+
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
1260+
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
1261+
{
1262+
return obj?.StructField;
1263+
}
1264+
1265+
/// <summary>
1266+
/// Implicit conversion to the type <see cref=""int""/>.
1267+
/// </summary>
1268+
/// <param name=""obj"">Object to covert.</param>
1269+
/// <returns>The <see cref=""StructField""/> of provided <paramref name=""obj""/>.</returns>
1270+
public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj)
1271+
{
1272+
return obj.StructField;
1273+
}
1274+
1275+
/// <summary>
1276+
/// Explicit conversion from the type <see cref=""int""/>.
1277+
/// </summary>
1278+
/// <param name=""structField"">Value to covert.</param>
1279+
/// <returns>An instance of <see cref=""TestValueObject""/>.</returns>
1280+
public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField)
1281+
{
1282+
return global::Thinktecture.Tests.TestValueObject.Create(structField);
1283+
}
1284+
1285+
private TestValueObject(int structField)
1286+
{
1287+
ValidateConstructorArguments(ref structField);
1288+
1289+
this.StructField = structField;
1290+
}
1291+
1292+
static partial void ValidateConstructorArguments(ref int structField);
1293+
1294+
/// <summary>
1295+
/// Compares to instances of <see cref=""TestValueObject""/>.
1296+
/// </summary>
1297+
/// <param name=""obj"">Instance to compare.</param>
1298+
/// <param name=""other"">Another instance to compare.</param>
1299+
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
1300+
public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other)
1301+
{
1302+
return obj.Equals(other);
1303+
}
1304+
1305+
/// <summary>
1306+
/// Compares to instances of <see cref=""TestValueObject""/>.
1307+
/// </summary>
1308+
/// <param name=""obj"">Instance to compare.</param>
1309+
/// <param name=""other"">Another instance to compare.</param>
1310+
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
1311+
public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other)
1312+
{
1313+
return !(obj == other);
1314+
}
1315+
1316+
/// <inheritdoc />
1317+
public override bool Equals(object? other)
1318+
{
1319+
return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj);
1320+
}
1321+
1322+
/// <inheritdoc />
1323+
public bool Equals(global::Thinktecture.Tests.TestValueObject other)
1324+
{
1325+
return this.StructField.Equals(other.StructField);
1326+
}
1327+
1328+
/// <inheritdoc />
1329+
public override int GetHashCode()
1330+
{
1331+
return global::System.HashCode.Combine(this.StructField);
1332+
}
1333+
1334+
/// <inheritdoc />
1335+
public override string? ToString()
1336+
{
1337+
return this.StructField.ToString();
1338+
}
1339+
1340+
/// <inheritdoc />
1341+
public string ToString(string? format, global::System.IFormatProvider? formatProvider = null)
1342+
{
1343+
return this.StructField.ToString(format, formatProvider);
1344+
}
1345+
1346+
/// <inheritdoc />
1347+
public int CompareTo(object? obj)
1348+
{
1349+
if(obj is null)
1350+
return 1;
1351+
1352+
if(obj is not global::Thinktecture.Tests.TestValueObject valueObject)
1353+
throw new global::System.ArgumentException(""Argument must be of type \""TestValueObject\""."", nameof(obj));
1354+
1355+
return this.CompareTo(valueObject);
1356+
}
1357+
1358+
/// <inheritdoc />
1359+
public int CompareTo(global::Thinktecture.Tests.TestValueObject obj)
1360+
{
1361+
return this.StructField.CompareTo(obj.StructField);
1362+
}
1363+
}
1364+
}
1365+
");
1366+
}
1367+
11541368
[Fact]
11551369
public void Should_generate_struct_with_string_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored()
11561370
{
@@ -1261,9 +1475,9 @@ internal static void ModuleInit()
12611475
/// <param name=""obj"">Object to covert.</param>
12621476
/// <returns>The <see cref=""ReferenceField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
12631477
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
1264-
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject obj)
1478+
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
12651479
{
1266-
return obj.ReferenceField;
1480+
return obj?.ReferenceField;
12671481
}
12681482
12691483
/// <summary>
@@ -1466,7 +1680,7 @@ internal static void ModuleInit()
14661680
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
14671681
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
14681682
{
1469-
return obj is null ? null : obj.ReferenceField;
1683+
return obj?.ReferenceField;
14701684
}
14711685
14721686
/// <summary>
@@ -1684,7 +1898,7 @@ internal static void ModuleInit()
16841898
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
16851899
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
16861900
{
1687-
return obj is null ? null : obj.StructField;
1901+
return obj?.StructField;
16881902
}
16891903
16901904
/// <summary>
@@ -1929,7 +2143,7 @@ internal static void ModuleInit()
19292143
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
19302144
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
19312145
{
1932-
return obj is null ? null : obj.ReferenceField;
2146+
return obj?.ReferenceField;
19332147
}
19342148
19352149
/// <summary>
@@ -2147,7 +2361,7 @@ internal static void ModuleInit()
21472361
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
21482362
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
21492363
{
2150-
return obj is null ? null : obj.StructField;
2364+
return obj?.StructField;
21512365
}
21522366
21532367
/// <summary>
@@ -2385,7 +2599,7 @@ internal static void ModuleInit()
23852599
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
23862600
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
23872601
{
2388-
return obj is null ? null : obj.ReferenceField;
2602+
return obj?.ReferenceField;
23892603
}
23902604
23912605
/// <summary>
@@ -2604,7 +2818,7 @@ internal static void ModuleInit()
26042818
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
26052819
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
26062820
{
2607-
return obj is null ? null : obj.ReferenceField;
2821+
return obj?.ReferenceField;
26082822
}
26092823
26102824
/// <summary>
@@ -2846,7 +3060,7 @@ internal static void ModuleInit()
28463060
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
28473061
public static implicit operator global::Thinktecture.Tests.Foo?(global::Thinktecture.Tests.TestValueObject? obj)
28483062
{
2849-
return obj is null ? null : obj.ReferenceField;
3063+
return obj?.ReferenceField;
28503064
}
28513065
28523066
/// <summary>

0 commit comments

Comments
 (0)