diff --git a/Directory.Build.props b/Directory.Build.props index 25cf660d..bb7c2745 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ (c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved. - 5.0.0 + 5.0.1 Pawel Gerr true https://github.com/PawelGerr/Thinktecture.Runtime.Extensions diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs index 0404462b..f719335e 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs @@ -211,22 +211,27 @@ private void GenerateImplicitConversionToKey(EqualityInstanceMemberInfo keyMembe /// Object to covert. /// The of provided or default if is null. [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($@" + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided . + public static implicit operator {keyMember.TypeFullyQualified}({_state.TypeFullyQualified} obj) + {{ + return obj.{keyMember.Name}; + }}"); } private void GenerateExplicitConversionToKey(EqualityInstanceMemberInfo keyMemberInfo) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs index 9ca6f77b..a312ce58 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs @@ -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; } @@ -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(); diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs index ebb43bd4..0cec343b 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs @@ -1056,9 +1056,9 @@ internal static void ModuleInit() /// Object to covert. /// The of provided or default if is null. [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; } /// @@ -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(source, typeof(ValueObjectAttribute).Assembly); + AssertOutput(output, _GENERATED_HEADER + @" +namespace Thinktecture.Tests +{ + public class TestValueObject_ValueObjectTypeConverter : global::Thinktecture.ValueObjectTypeConverter + { + /// + protected override global::Thinktecture.Tests.TestValueObject ConvertFrom(int structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + /// + 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::System.IFormattable, global::System.IComparable, global::System.IComparable + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + var convertFromKey = new global::System.Func(global::Thinktecture.Tests.TestValueObject.Create); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static structField => global::Thinktecture.Tests.TestValueObject.Create(structField); + global::System.Linq.Expressions.Expression> convertFromKeyExpressionViaCtor = static structField => new global::Thinktecture.Tests.TestValueObject(structField); + + var convertToKey = new global::System.Func(static item => item.StructField); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static obj => obj.StructField; + + var tryCreate = new global::Thinktecture.Internal.Validate(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(); + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(""obj"")] + public static implicit operator int?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Implicit conversion to the type . + /// + /// Object to covert. + /// The of provided . + public static implicit operator int(global::Thinktecture.Tests.TestValueObject obj) + { + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + 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); + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return obj.Equals(other); + } + + /// + /// Compares to instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject obj, global::Thinktecture.Tests.TestValueObject other) + { + return !(obj == other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) + { + return this.StructField.Equals(other.StructField); + } + + /// + public override int GetHashCode() + { + return global::System.HashCode.Combine(this.StructField); + } + + /// + public override string? ToString() + { + return this.StructField.ToString(); + } + + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } + + /// + 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); + } + + /// + 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() { @@ -1261,9 +1475,9 @@ internal static void ModuleInit() /// Object to covert. /// The of provided or default if is null. [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; } /// @@ -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; } /// @@ -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; } /// @@ -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; } /// @@ -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; } /// @@ -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; } /// @@ -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; } /// @@ -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; } ///