Skip to content

Commit

Permalink
Struct-ValueObjects with struct-key-member need 2 implicit conversions.
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Oct 6, 2022
1 parent 040f136 commit bbc972d
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
<VersionPrefix>5.0.0</VersionPrefix>
<VersionPrefix>5.0.1</VersionPrefix>
<Authors>Pawel Gerr</Authors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,22 +211,27 @@ private void GenerateImplicitConversionToKey(EqualityInstanceMemberInfo keyMembe
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""{keyMember.Name}""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator {keyMember.TypeFullyQualifiedNullable}({_state.TypeFullyQualifiedNullAnnotated} obj)
{{");
public static implicit operator {keyMember.TypeFullyQualifiedNullable}({(_state.IsReferenceType ? _state.TypeFullyQualifiedNullAnnotated : _state.TypeFullyQualifiedNullable)} obj)
{{
return obj?.{keyMember.Name};
}}");

if (_state.IsReferenceType)
{
_sb.Append($@"
return obj is null ? null : obj.{keyMember.Name};");
}
else
{
_sb.Append($@"
return obj.{keyMember.Name};");
}
if (_state.IsReferenceType || keyMember.IsReferenceType)
return;

_sb.Append(@"
}");
// if value object and key member are structs

_sb.Append($@"
/// <summary>
/// Implicit conversion to the type <see cref=""{keyMember.TypeMinimallyQualified}""/>.
/// </summary>
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""{keyMember.Name}""/> of provided <paramref name=""obj""/>.</returns>
public static implicit operator {keyMember.TypeFullyQualified}({_state.TypeFullyQualified} obj)
{{
return obj.{keyMember.Name};
}}");
}

private void GenerateExplicitConversionToKey(EqualityInstanceMemberInfo keyMemberInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public sealed class ValueObjectSourceGeneratorState :
private readonly INamedTypeSymbol _type;

public string TypeFullyQualified { get; }
public string TypeFullyQualifiedNullable { get; }
public string TypeFullyQualifiedNullAnnotated { get; }
public string TypeMinimallyQualified { get; }

Expand Down Expand Up @@ -42,7 +43,8 @@ public ValueObjectSourceGeneratorState(

Namespace = type.ContainingNamespace?.IsGlobalNamespace == true ? null : type.ContainingNamespace?.ToString();
TypeFullyQualified = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
TypeFullyQualifiedNullAnnotated = type.IsReferenceType ? $"{TypeFullyQualified}?" : TypeFullyQualified;
TypeFullyQualifiedNullable = $"{TypeFullyQualified}?";
TypeFullyQualifiedNullAnnotated = type.IsReferenceType ? TypeFullyQualifiedNullable : TypeFullyQualified;
TypeMinimallyQualified = type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);

AssignableInstanceFieldsAndProperties = _type.GetAssignableFieldsAndPropertiesAndCheckForReadOnly(true, cancellationToken).ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,9 +1056,9 @@ internal static void ModuleInit()
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""ReferenceField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject obj)
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -1151,6 +1151,220 @@ public int CompareTo(global::Thinktecture.Tests.TestValueObject obj)
");
}

[Fact]
public void Should_generate_struct_with_int_key_member()
{
var source = @"
using System;
using Thinktecture;
namespace Thinktecture.Tests
{
[ValueObject]
public readonly partial struct TestValueObject
{
public readonly int StructField;
}
}
";
var output = GetGeneratedOutput<ValueObjectSourceGenerator>(source, typeof(ValueObjectAttribute).Assembly);
AssertOutput(output, _GENERATED_HEADER + @"
namespace Thinktecture.Tests
{
public class TestValueObject_ValueObjectTypeConverter : global::Thinktecture.ValueObjectTypeConverter<global::Thinktecture.Tests.TestValueObject, int>
{
/// <inheritdoc />
protected override global::Thinktecture.Tests.TestValueObject ConvertFrom(int structField)
{
return global::Thinktecture.Tests.TestValueObject.Create(structField);
}
/// <inheritdoc />
protected override int GetKeyValue(global::Thinktecture.Tests.TestValueObject obj)
{
return (int) obj;
}
}
[global::Thinktecture.Internal.ValueObjectConstructor(nameof(StructField))]
[global::Thinktecture.Internal.KeyedValueObject]
[global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.Tests.TestValueObject_ValueObjectTypeConverter))]
partial struct TestValueObject : global::System.IEquatable<global::Thinktecture.Tests.TestValueObject>, global::System.IFormattable, global::System.IComparable, global::System.IComparable<global::Thinktecture.Tests.TestValueObject>
{
[global::System.Runtime.CompilerServices.ModuleInitializer]
internal static void ModuleInit()
{
var convertFromKey = new global::System.Func<int, global::Thinktecture.Tests.TestValueObject>(global::Thinktecture.Tests.TestValueObject.Create);
global::System.Linq.Expressions.Expression<global::System.Func<int, global::Thinktecture.Tests.TestValueObject>> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField);
global::System.Linq.Expressions.Expression<global::System.Func<int, global::Thinktecture.Tests.TestValueObject>> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField);
var convertToKey = new global::System.Func<global::Thinktecture.Tests.TestValueObject, int>(static item => item.StructField);
global::System.Linq.Expressions.Expression<global::System.Func<global::Thinktecture.Tests.TestValueObject, int>> convertToKeyExpression = static obj => obj.StructField;
var tryCreate = new global::Thinktecture.Internal.Validate<global::Thinktecture.Tests.TestValueObject, int>(global::Thinktecture.Tests.TestValueObject.TryCreate);
var type = typeof(global::Thinktecture.Tests.TestValueObject);
var metadata = new global::Thinktecture.Internal.ValueObjectMetadata(type, typeof(int), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression, tryCreate);
global::Thinktecture.Internal.ValueObjectMetadataLookup.AddMetadata(type, metadata);
}
private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject);
public static readonly global::Thinktecture.Tests.TestValueObject Empty = default;
public static global::Thinktecture.Tests.TestValueObject Create(int structField)
{
var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success;
ValidateFactoryArguments(ref validationResult, ref structField);
if(validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success)
throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? ""Validation failed."");
var obj = new global::Thinktecture.Tests.TestValueObject(structField);
obj.FactoryPostInit();
return obj;
}
public static global::System.ComponentModel.DataAnnotations.ValidationResult? TryCreate(
int structField,
[global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestValueObject obj)
{
var validationResult = global::System.ComponentModel.DataAnnotations.ValidationResult.Success;
ValidateFactoryArguments(ref validationResult, ref structField);
if (validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success)
{
obj = new global::Thinktecture.Tests.TestValueObject(structField);
obj.FactoryPostInit();
}
else
{
obj = default;
}
return validationResult;
}
static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref int structField);
partial void FactoryPostInit();
/// <summary>
/// Implicit conversion to the type <see cref=""int""/>.
/// </summary>
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""StructField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj?.StructField;
}
/// <summary>
/// Implicit conversion to the type <see cref=""int""/>.
/// </summary>
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""StructField""/> of provided <paramref name=""obj""/>.</returns>
public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj)
{
return obj.StructField;
}
/// <summary>
/// Explicit conversion from the type <see cref=""int""/>.
/// </summary>
/// <param name=""structField"">Value to covert.</param>
/// <returns>An instance of <see cref=""TestValueObject""/>.</returns>
public static explicit operator global::Thinktecture.Tests.TestValueObject(int structField)
{
return global::Thinktecture.Tests.TestValueObject.Create(structField);
}
private TestValueObject(int structField)
{
ValidateConstructorArguments(ref structField);
this.StructField = structField;
}
static partial void ValidateConstructorArguments(ref int structField);
/// <summary>
/// Compares to instances of <see cref=""TestValueObject""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""other"">Another instance to compare.</param>
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other)
{
return obj.Equals(other);
}
/// <summary>
/// Compares to instances of <see cref=""TestValueObject""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""other"">Another instance to compare.</param>
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other)
{
return !(obj == other);
}
/// <inheritdoc />
public override bool Equals(object? other)
{
return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj);
}
/// <inheritdoc />
public bool Equals(global::Thinktecture.Tests.TestValueObject other)
{
return this.StructField.Equals(other.StructField);
}
/// <inheritdoc />
public override int GetHashCode()
{
return global::System.HashCode.Combine(this.StructField);
}
/// <inheritdoc />
public override string? ToString()
{
return this.StructField.ToString();
}
/// <inheritdoc />
public string ToString(string? format, global::System.IFormatProvider? formatProvider = null)
{
return this.StructField.ToString(format, formatProvider);
}
/// <inheritdoc />
public int CompareTo(object? obj)
{
if(obj is null)
return 1;
if(obj is not global::Thinktecture.Tests.TestValueObject valueObject)
throw new global::System.ArgumentException(""Argument must be of type \""TestValueObject\""."", nameof(obj));
return this.CompareTo(valueObject);
}
/// <inheritdoc />
public int CompareTo(global::Thinktecture.Tests.TestValueObject obj)
{
return this.StructField.CompareTo(obj.StructField);
}
}
}
");
}

[Fact]
public void Should_generate_struct_with_string_key_member_and_NullInFactoryMethodsYieldsNull_should_be_ignored()
{
Expand Down Expand Up @@ -1261,9 +1475,9 @@ internal static void ModuleInit()
/// <param name=""obj"">Object to covert.</param>
/// <returns>The <see cref=""ReferenceField""/> of provided <paramref name=""obj""/> or <c>default</c> if <paramref name=""obj""/> is <c>null</c>.</returns>
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject obj)
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -1466,7 +1680,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -1684,7 +1898,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.StructField;
return obj?.StructField;
}
/// <summary>
Expand Down Expand Up @@ -1929,7 +2143,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -2147,7 +2361,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.StructField;
return obj?.StructField;
}
/// <summary>
Expand Down Expand Up @@ -2385,7 +2599,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator string?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -2604,7 +2818,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down Expand Up @@ -2846,7 +3060,7 @@ internal static void ModuleInit()
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")]
public static implicit operator global::Thinktecture.Tests.Foo?(global::Thinktecture.Tests.TestValueObject? obj)
{
return obj is null ? null : obj.ReferenceField;
return obj?.ReferenceField;
}
/// <summary>
Expand Down

0 comments on commit bbc972d

Please sign in to comment.