diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/EndDate.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/EndDate.cs index 679ae59a..fe0aa0fc 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/EndDate.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/EndDate.cs @@ -2,12 +2,14 @@ namespace Thinktecture.ValueObjects; -[ValueObject(DefaultInstancePropertyName = "Infinite")] +[ValueObject(DefaultInstancePropertyName = "Infinite", + EqualityComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] // for comparison with DateTime without implicit cast public readonly partial struct EndDate { [ValueObjectMemberIgnore] private readonly DateOnly? _date; + // can be public as well private DateOnly Date { get => _date ?? DateOnly.MaxValue; diff --git a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs index b4bea651..1af7de89 100644 --- a/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs +++ b/samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ValueObjectDemos.cs @@ -102,7 +102,7 @@ private static void DemoForEndDate(ILogger logger) logger.Information("DateOnly of EndDate.Infinite and default(EndDate) are equal: {AreEqual}", dateOfInfiniteDate == dateOfDefaultDate); - logger.Information("EndDate.Infinite and DateOnly.MaxValue are equal: {AreEqual}", dateOfInfiniteDate == DateOnly.MaxValue); + logger.Information("EndDate.Infinite and DateOnly.MaxValue are equal: {AreEqual}", infiniteEndDate == DateOnly.MaxValue); } private static void DemoForComplexValueObjects(ILogger logger) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparerInfo.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparerInfo.cs new file mode 100644 index 00000000..80956521 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparerInfo.cs @@ -0,0 +1,3 @@ +namespace Thinktecture.CodeAnalysis; + +public readonly record struct ComparerInfo(string Comparer, bool IsAccessor); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs index d146127f..e18cc406 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ComparisonOperatorsCodeGenerator.cs @@ -65,6 +65,12 @@ public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberIn sb.Append(@" global::System.Numerics.IComparisonOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", bool>"); + + if (!_withKeyTypeOverloads) + return; + + sb.Append(@", + global::System.Numerics.IComparisonOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", bool>"); } public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs index 95a143e8..aa7c1988 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DefaultMemberState.cs @@ -38,10 +38,10 @@ public bool Equals(IMemberState? obj) public bool Equals(DefaultMemberState? other) { + if (other is null) + return false; if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) - return false; return _typedMemberState.Equals(other._typedMemberState) && Name == other.Name diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs new file mode 100644 index 00000000..bd0a7b1b --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsCodeGenerator.cs @@ -0,0 +1,218 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Thinktecture.CodeAnalysis; + +public sealed class EqualityComparisonOperatorsCodeGenerator : IInterfaceCodeGenerator +{ + private static readonly IInterfaceCodeGenerator _default = new EqualityComparisonOperatorsCodeGenerator(false, null); + private static readonly IInterfaceCodeGenerator _defaultWithKeyTypeOverloads = new EqualityComparisonOperatorsCodeGenerator(true, null); + + public static bool TryGet( + OperatorsGeneration operatorsGeneration, + ComparerInfo? equalityComparer, + [MaybeNullWhen(false)] out IInterfaceCodeGenerator generator) + { + switch (operatorsGeneration) + { + case OperatorsGeneration.None: + generator = null; + return false; + case OperatorsGeneration.Default: + generator = equalityComparer is null + ? _default + : new EqualityComparisonOperatorsCodeGenerator(false, equalityComparer); + return true; + case OperatorsGeneration.DefaultWithKeyTypeOverloads: + generator = equalityComparer is null + ? _defaultWithKeyTypeOverloads + : new EqualityComparisonOperatorsCodeGenerator(true, equalityComparer); + return true; + default: + throw new ArgumentOutOfRangeException(nameof(operatorsGeneration), operatorsGeneration, "Invalid operations generation."); + } + } + + private readonly bool _withKeyTypeOverloads; + private readonly ComparerInfo? _equalityComparer; + + public string CodeGeneratorName => "EqualityComparisonOperators-CodeGenerator"; + public string FileNameSuffix => ".EqualityComparisonOperators"; + + private EqualityComparisonOperatorsCodeGenerator( + bool withKeyTypeOverloads, + ComparerInfo? equalityComparer) + { + _withKeyTypeOverloads = withKeyTypeOverloads; + _equalityComparer = equalityComparer; + } + + public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + { + sb.Append(@" + global::System.Numerics.IEqualityOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", bool>"); + + if (!_withKeyTypeOverloads) + return; + + sb.Append(@", + global::System.Numerics.IEqualityOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", bool>"); + } + + public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) + { + GenerateUsingEquals(sb, type); + + if (_withKeyTypeOverloads) + GenerateKeyOverloads(sb, type, keyMember); + } + + private static void GenerateUsingEquals(StringBuilder sb, ITypeInformation type) + { + sb.Append(@" + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" other) + {"); + + if (type.IsReferenceType) + { + if (type.IsEqualWithReferenceEquality) + { + sb.Append(@" + return global::System.Object.ReferenceEquals(obj, other);"); + } + else + { + sb.Append(@" + if (obj is null) + return other is null; + + return obj.Equals(other);"); + } + } + else + { + sb.Append(@" + return obj.Equals(other);"); + } + + sb.Append(@" + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" other) + { + return !(obj == other); + }"); + } + + private void GenerateKeyOverloads( + StringBuilder sb, + ITypeInformation type, + IMemberInformation keyMember) + { + sb.Append(@" + + private static bool Equals(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + {"); + + if (type.IsReferenceType) + { + if (keyMember.IsReferenceType) + { + sb.Append(@" + if (obj is null) + return value is null; +"); + } + else + { + sb.Append(@" + if (obj is null) + return false; +"); + } + } + + sb.Append(@" + return "); + + if (_equalityComparer == null) + { + if (keyMember.IsReferenceType) + { + sb.Append("obj.").Append(keyMember.Name).Append(" is null ? value").Append(" is null : obj.").Append(keyMember.Name).Append(".Equals(value").Append(")"); + } + else + { + sb.Append("obj.").Append(keyMember.Name).Append(".Equals(value)"); + } + } + else + { + sb.Append(_equalityComparer.Value.Comparer); + + if (_equalityComparer.Value.IsAccessor) + sb.Append(".EqualityComparer"); + + sb.Append(".Equals(obj.").Append(keyMember.Name).Append(", value)"); + } + + sb.Append(@"; + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj) + { + return !(obj == value); + }"); + } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsGeneratorState.cs new file mode 100644 index 00000000..e8bcecd9 --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityComparisonOperatorsGeneratorState.cs @@ -0,0 +1,47 @@ +namespace Thinktecture.CodeAnalysis; + +public readonly struct EqualityComparisonOperatorsGeneratorState : IEquatable +{ + public ITypeInformation Type { get; } + public IMemberInformation KeyMember { get; } + public OperatorsGeneration OperatorsGeneration { get; } + public ComparerInfo? EqualityComparer { get; } + + public EqualityComparisonOperatorsGeneratorState( + ITypeInformation type, + IMemberInformation keyMember, + OperatorsGeneration operatorsGeneration, + ComparerInfo? equalityComparer) + { + Type = type; + KeyMember = keyMember; + OperatorsGeneration = operatorsGeneration; + EqualityComparer = equalityComparer; + } + + public bool Equals(EqualityComparisonOperatorsGeneratorState other) + { + return TypeInformationComparer.Instance.Equals(Type, other.Type) + && MemberInformationComparer.Instance.Equals(KeyMember, other.KeyMember) + && OperatorsGeneration == other.OperatorsGeneration + && EqualityComparer == other.EqualityComparer; + } + + public override bool Equals(object? obj) + { + return obj is EqualityComparisonOperatorsGeneratorState state && Equals(state); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = TypeInformationComparer.Instance.GetHashCode(Type); + hashCode = (hashCode * 397) ^ MemberInformationComparer.Instance.GetHashCode(KeyMember); + hashCode = (hashCode * 397) ^ (int)OperatorsGeneration; + hashCode = (hashCode * 397) ^ EqualityComparer?.GetHashCode() ?? 0; + + return hashCode; + } + } +} diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityInstanceMemberInfo.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityInstanceMemberInfo.cs index 900eacbe..90b940c9 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityInstanceMemberInfo.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/EqualityInstanceMemberInfo.cs @@ -29,7 +29,7 @@ public override bool Equals(object? obj) public bool Equals(EqualityInstanceMemberInfo? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformation.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformation.cs index cf4651ea..c578455f 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformation.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ITypeInformation.cs @@ -5,4 +5,5 @@ public interface ITypeInformation : INamespaceAndName, ITypeFullyQualified string TypeMinimallyQualified { get; } string TypeFullyQualifiedNullAnnotated { get; } bool IsReferenceType { get; } + bool IsEqualWithReferenceEquality { get; } } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs index bd6d57b9..ae3d8982 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/InstanceMemberInfo.cs @@ -104,7 +104,7 @@ public bool Equals(IMemberState? obj) public bool Equals(InstanceMemberInfo? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/BaseTypeState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/BaseTypeState.cs index 7d05a2e3..76886b44 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/BaseTypeState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/BaseTypeState.cs @@ -20,7 +20,7 @@ public override bool Equals(object? obj) public bool Equals(BaseTypeState? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/ConstructorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/ConstructorState.cs index 56830657..fc598e7b 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/ConstructorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/ConstructorState.cs @@ -11,7 +11,7 @@ public ConstructorState(IReadOnlyList arguments) public bool Equals(ConstructorState? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; @@ -21,7 +21,7 @@ public bool Equals(ConstructorState? other) public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs index e697cd0f..52699bb0 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs @@ -8,6 +8,7 @@ public sealed class EnumSettings : IEquatable public bool SkipIComparable { get; } public bool SkipIParsable { get; } public OperatorsGeneration ComparisonOperators { get; } + public OperatorsGeneration EqualityComparisonOperators { get; } public bool SkipIFormattable { get; } public bool SkipToString { get; } @@ -17,8 +18,13 @@ public EnumSettings(AttributeData? attribute) SkipIComparable = attribute?.FindSkipIComparable() ?? false; SkipIParsable = attribute?.FindSkipIParsable() ?? false; ComparisonOperators = attribute?.FindComparisonOperators() ?? OperatorsGeneration.Default; + EqualityComparisonOperators = attribute?.FindEqualityComparisonOperators() ?? OperatorsGeneration.Default; SkipIFormattable = attribute?.FindSkipIFormattable() ?? false; SkipToString = attribute?.FindSkipToString() ?? false; + + // Comparison operators depend on the equality comparison operators + if (ComparisonOperators > EqualityComparisonOperators) + EqualityComparisonOperators = ComparisonOperators; } public override bool Equals(object? obj) @@ -28,7 +34,7 @@ public override bool Equals(object? obj) public bool Equals(EnumSettings? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; @@ -37,6 +43,7 @@ public bool Equals(EnumSettings? other) && SkipIComparable == other.SkipIComparable && SkipIParsable == other.SkipIParsable && ComparisonOperators == other.ComparisonOperators + && EqualityComparisonOperators == other.EqualityComparisonOperators && SkipIFormattable == other.SkipIFormattable && SkipToString == other.SkipToString; } @@ -49,6 +56,7 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ SkipIComparable.GetHashCode(); hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode(); hashCode = (hashCode * 397) ^ ComparisonOperators.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode(); hashCode = (hashCode * 397) ^ SkipIFormattable.GetHashCode(); hashCode = (hashCode * 397) ^ SkipToString.GetHashCode(); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs index 1de85f4e..d2505b42 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs @@ -9,6 +9,7 @@ public sealed class EnumSourceGeneratorState : ITypeInformation, IEquatable !IsValidatable; public IMemberState KeyProperty { get; } public bool IsValidatable { get; } @@ -78,7 +79,7 @@ public override bool Equals(object? obj) public bool Equals(EnumSourceGeneratorState? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs index 3ae64ef4..1ccb5797 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs @@ -53,8 +53,7 @@ private void GenerateEnum(CancellationToken cancellationToken) _sb.Append(@" [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.KeyProperty.TypeFullyQualified).Append(@">))] partial ").Append(_state.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Name).Append(" : global::Thinktecture.IEnum<").Append(_state.KeyProperty.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(@">, - global::System.IEquatable<").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@">, - global::System.Numerics.IEqualityOperators<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(@", bool> + global::System.IEquatable<").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@"> {"); GenerateModuleInitializer(_state.KeyProperty); @@ -127,7 +126,6 @@ private void GenerateEnum(CancellationToken cancellationToken) GenerateValidate(); GenerateImplicitConversion(); GenerateExplicitConversion(); - GenerateEqualityOperators(); GenerateEquals(); cancellationToken.ThrowIfCancellationRequested(); @@ -439,53 +437,6 @@ private void GenerateValidate() }"); } - private void GenerateEqualityOperators() - { - _sb.Append(@" - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(").Append(_state.TypeFullyQualifiedNullAnnotated).Append(" item1, ").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" item2) - {"); - - if (_state.IsValidatable) - { - if (_state.IsReferenceType) - { - _sb.Append(@" - if (item1 is null) - return item2 is null; -"); - } - - _sb.Append(@" - return item1.Equals(item2);"); - } - else - { - _sb.Append(@" - return global::System.Object.ReferenceEquals(item1, item2);"); - } - - _sb.Append(@" - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(").Append(_state.TypeFullyQualifiedNullAnnotated).Append(" item1, ").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" item2) - { - return !(item1 == item2); - }"); - } - private void GenerateImplicitConversion() { _sb.Append(@" @@ -564,6 +515,7 @@ public bool Equals(").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" o } else { + // ReferenceEquals is ok because non-validatable smart enums are classes only _sb.Append(@" return global::System.Object.ReferenceEquals(this, other);"); } @@ -720,7 +672,7 @@ private void GenerateCreateAndCheckInvalidItem(bool needsCreateInvalidItemImplem private static ").Append(_state.TypeFullyQualified).Append(" CreateAndCheckInvalidItem(").Append(_state.KeyProperty.TypeFullyQualified).Append(" ").Append(_state.KeyProperty.ArgumentName).Append(@") { - var item = "); + var item = "); if (needsCreateInvalidItemImplementation && _state.IsAbstract) { @@ -737,24 +689,25 @@ private void GenerateCreateAndCheckInvalidItem(bool needsCreateInvalidItemImplem if (_state.IsReferenceType) { _sb.Append(@" - if (item is null) - throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append(@"' must not return 'null'.""); + if (item is null) + throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append(@"' must not return 'null'.""); "); } _sb.Append(@" - if (item.IsValid) - throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append(@"' must return an instance with property 'IsValid' equals to 'false'."");"); + if (item.IsValid) + throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append(@"' must return an instance with property 'IsValid' equals to 'false'."");"); if (!needsCreateInvalidItemImplementation) { _sb.Append(@" - if (_itemsLookup.Value.ContainsKey(item.").Append(_state.KeyProperty.Name).Append(@")) - throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append("' must not return an instance with property '").Append(_state.KeyProperty.Name).Append(@"' equals to one of a valid item."");"); + if (_itemsLookup.Value.ContainsKey(item.").Append(_state.KeyProperty.Name).Append(@")) + throw new global::System.Exception(""The implementation of method '").Append(Constants.Methods.CREATE_INVALID_ITEM).Append("' must not return an instance with property '").Append(_state.KeyProperty.Name).Append(@"' equals to one of a valid item."");"); } _sb.Append(@" + return item; }"); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs index 12e08e63..b1ef6645 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs @@ -36,6 +36,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) InitializeComparableCodeGenerator(context, validStates, options); InitializeParsableCodeGenerator(context, validStates, options); InitializeComparisonOperatorsCodeGenerator(context, validStates, options); + InitializeEqualityComparisonOperatorsCodeGenerator(context, validStates, options); InitializeErrorReporting(context, enumTypeOrError); InitializeExceptionReporting(context, enumTypeOrError); @@ -53,6 +54,17 @@ private void InitializeComparisonOperatorsCodeGenerator(IncrementalGeneratorInit InitializeComparisonOperatorsCodeGenerator(context, comparables, options); } + private void InitializeEqualityComparisonOperatorsCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) + { + var comparables = validStates + .Select((state, _) => new EqualityComparisonOperatorsGeneratorState(state.State, + state.KeyMember, + state.Settings.EqualityComparisonOperators, + new ComparerInfo(Constants.KEY_EQUALITY_COMPARER_NAME, false))); + + InitializeEqualityComparisonOperatorsCodeGenerator(context, comparables, options); + } + private void InitializeParsableCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) { var parsables = validStates diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs index d6d4e040..646ff0d2 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ThinktectureSourceGeneratorBase.cs @@ -215,6 +215,36 @@ protected void InitializeComparisonOperatorsCodeGenerator( }); } + protected void InitializeEqualityComparisonOperatorsCodeGenerator( + IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider comparables, + IncrementalValueProvider options) + { + var operators = comparables + .Where(state => state.OperatorsGeneration != OperatorsGeneration.None) + .Collect() + .Select(static (states, _) => states.IsDefaultOrEmpty + ? ImmutableArray.Empty + : states.Distinct(TypeOnlyComparer.Instance).ToImmutableArray()) + .WithComparer(new SetComparer()) + .SelectMany((states, _) => states) + .SelectMany((state, _) => + { + if (EqualityComparisonOperatorsCodeGenerator.TryGet(state.OperatorsGeneration, state.EqualityComparer, out var codeGenerator)) + return ImmutableArray.Create((State: state, CodeGenerator: codeGenerator)); + + return ImmutableArray<(EqualityComparisonOperatorsGeneratorState State, IInterfaceCodeGenerator CodeGenerator)>.Empty; + }); + + context.RegisterSourceOutput(operators.Combine(options), (ctx, tuple) => + { + var state = tuple.Left.State; + var generator = tuple.Left.CodeGenerator; + + GenerateCode(ctx, state.Type.Namespace, state.Type.Name, (state.Type, state.KeyMember), tuple.Right, InterfaceCodeGeneratorFactory.Create(generator)); + }); + } + protected void InitializeOperatorsCodeGenerator( IncrementalGeneratorInitializationContext context, IncrementalValuesProvider operators, diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeOnlyComparer.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeOnlyComparer.cs index 2291b18c..c83c9c90 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeOnlyComparer.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypeOnlyComparer.cs @@ -5,6 +5,7 @@ public class TypeOnlyComparer IEqualityComparer, IEqualityComparer, IEqualityComparer, + IEqualityComparer, IEqualityComparer { public static readonly TypeOnlyComparer Instance = new(); @@ -13,12 +14,14 @@ public class TypeOnlyComparer public bool Equals(ComparableGeneratorState x, ComparableGeneratorState y) => x.Type.TypeFullyQualified == y.Type.TypeFullyQualified; public bool Equals(ParsableGeneratorState x, ParsableGeneratorState y) => x.Type.TypeFullyQualified == y.Type.TypeFullyQualified; public bool Equals(ComparisonOperatorsGeneratorState x, ComparisonOperatorsGeneratorState y) => x.Type.TypeFullyQualified == y.Type.TypeFullyQualified; + public bool Equals(EqualityComparisonOperatorsGeneratorState x, EqualityComparisonOperatorsGeneratorState y) => x.Type.TypeFullyQualified == y.Type.TypeFullyQualified; public bool Equals(OperatorsGeneratorState x, OperatorsGeneratorState y) => x.Type.TypeFullyQualified == y.Type.TypeFullyQualified; public int GetHashCode(FormattableGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); public int GetHashCode(ComparableGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); public int GetHashCode(ParsableGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); public int GetHashCode(ComparisonOperatorsGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); + public int GetHashCode(EqualityComparisonOperatorsGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); public int GetHashCode(OperatorsGeneratorState obj) => obj.Type.TypeFullyQualified.GetHashCode(); } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypedMemberState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypedMemberState.cs index 2f3c3cd5..b868da1e 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypedMemberState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/TypedMemberState.cs @@ -153,7 +153,7 @@ public bool Equals(ITypedMemberState? obj) public bool Equals(TypedMemberState? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AllValueObjectSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AllValueObjectSettings.cs index 565e280a..d6ba2ca7 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AllValueObjectSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/AllValueObjectSettings.cs @@ -16,6 +16,7 @@ public sealed class AllValueObjectSettings : IEquatable public OperatorsGeneration MultiplyOperators { get; } public OperatorsGeneration DivisionOperators { get; } public OperatorsGeneration ComparisonOperators { get; } + public OperatorsGeneration EqualityComparisonOperators { get; } public string DefaultInstancePropertyName { get; } public AllValueObjectSettings(AttributeData valueObjectAttribute) @@ -31,8 +32,13 @@ public AllValueObjectSettings(AttributeData valueObjectAttribute) SubtractionOperators = SkipFactoryMethods ? OperatorsGeneration.None : valueObjectAttribute.FindSubtractionOperators(); MultiplyOperators = SkipFactoryMethods ? OperatorsGeneration.None : valueObjectAttribute.FindMultiplyOperators(); DivisionOperators = SkipFactoryMethods ? OperatorsGeneration.None : valueObjectAttribute.FindDivisionOperators(); + EqualityComparisonOperators = valueObjectAttribute.FindEqualityComparisonOperators(); ComparisonOperators = valueObjectAttribute.FindComparisonOperators(); DefaultInstancePropertyName = valueObjectAttribute.FindDefaultInstancePropertyName() ?? "Empty"; + + // Comparison operators depend on the equality comparison operators + if (ComparisonOperators > EqualityComparisonOperators) + EqualityComparisonOperators = ComparisonOperators; } public override bool Equals(object? obj) @@ -42,7 +48,7 @@ public override bool Equals(object? obj) public bool Equals(AllValueObjectSettings? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; @@ -59,6 +65,7 @@ public bool Equals(AllValueObjectSettings? other) && MultiplyOperators == other.MultiplyOperators && DivisionOperators == other.DivisionOperators && ComparisonOperators == other.ComparisonOperators + && EqualityComparisonOperators == other.EqualityComparisonOperators && DefaultInstancePropertyName == other.DefaultInstancePropertyName; } @@ -78,6 +85,7 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ MultiplyOperators.GetHashCode(); hashCode = (hashCode * 397) ^ DivisionOperators.GetHashCode(); hashCode = (hashCode * 397) ^ ComparisonOperators.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode(); hashCode = (hashCode * 397) ^ DefaultInstancePropertyName.GetHashCode(); return hashCode; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs index 5ac9da37..580b487c 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/DivisionOperatorsCodeGenerator.cs @@ -56,11 +56,11 @@ public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberIn sb.Append(@" global::System.Numerics.IDivisionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - if (_withKeyTypeOverloads) - { - sb.Append(@", + if (!_withKeyTypeOverloads) + return; + + sb.Append(@", global::System.Numerics.IDivisionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - } } public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs index 066725c3..e9512ebb 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/MultiplyOperatorsCodeGenerator.cs @@ -56,11 +56,11 @@ public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberIn sb.Append(@" global::System.Numerics.IMultiplyOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - if (_withKeyTypeOverloads) - { - sb.Append(@", + if (!_withKeyTypeOverloads) + return; + + sb.Append(@", global::System.Numerics.IMultiplyOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - } } public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs index 4286ab87..fc0b0c29 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/SubtractionOperatorsCodeGenerator.cs @@ -59,11 +59,11 @@ public void GenerateBaseTypes(StringBuilder sb, ITypeInformation type, IMemberIn sb.Append(@" global::System.Numerics.ISubtractionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - if (_withKeyTypeOverloads) - { - sb.Append(@", + if (!_withKeyTypeOverloads) + return; + + sb.Append(@", global::System.Numerics.ISubtractionOperators<").Append(type.TypeFullyQualified).Append(", ").Append(keyMember.TypeFullyQualified).Append(", ").Append(type.TypeFullyQualified).Append(">"); - } } public void GenerateImplementation(StringBuilder sb, ITypeInformation type, IMemberInformation keyMember) diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs index c854f40c..9d26e991 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectCodeGenerator.cs @@ -55,8 +55,7 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c } _sb.Append(@" - partial ").Append(_state.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Name).Append(" : global::System.IEquatable<").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@">, - global::System.Numerics.IEqualityOperators<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(", bool>"); + partial ").Append(_state.IsReferenceType ? "class" : "struct").Append(" ").Append(_state.Name).Append(" : global::System.IEquatable<").Append(_state.TypeFullyQualifiedNullAnnotated).Append(">"); if (_state.HasKeyMember) { @@ -72,6 +71,7 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c else { _sb.Append(@", + global::System.Numerics.IEqualityOperators<").Append(_state.TypeFullyQualified).Append(", ").Append(_state.TypeFullyQualified).Append(@", bool>, global::Thinktecture.IComplexValueObject"); } @@ -124,7 +124,11 @@ private void GenerateValueObject(bool emptyStringYieldsNull, CancellationToken c cancellationToken.ThrowIfCancellationRequested(); GenerateConstructor(); - GenerateEqualityOperators(); + + // Keyed value object get their equality operators from EqualityComparisonOperatorsCodeGenerator + if (!_state.HasKeyMember) + GenerateEqualityOperators(); + GenerateEquals(); GenerateGetHashCode(); @@ -564,7 +568,7 @@ private void GenerateConstructor() var isStructDefaultCtor = !_state.IsReferenceType && fieldsAndProperties.Count == 0; - if(isStructDefaultCtor) + if (isStructDefaultCtor) return; _sb.Append(@" @@ -628,9 +632,6 @@ public bool Equals(").Append(_state.TypeFullyQualifiedNullAnnotated).Append(@" o if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; "); diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectMemberSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectMemberSettings.cs index 21758f54..11c4bb1e 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectMemberSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectMemberSettings.cs @@ -83,7 +83,7 @@ public override bool Equals(object? obj) public bool Equals(ValueObjectMemberSettings? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs index ef6c7cf1..599b82e6 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGenerator.cs @@ -46,6 +46,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) InitializeComparableCodeGenerator(context, keyedValueObjects, options); InitializeParsableCodeGenerator(context, keyedValueObjects, options); InitializeComparisonOperatorsCodeGenerator(context, keyedValueObjects, options); + InitializeEqualityComparisonOperatorsCodeGenerator(context, keyedValueObjects, options); InitializeAdditionOperatorsCodeGenerator(context, keyedValueObjects, options); InitializeSubtractionOperatorsCodeGenerator(context, keyedValueObjects, options); InitializeMultiplyOperatorsCodeGenerator(context, keyedValueObjects, options); @@ -178,6 +179,17 @@ private void InitializeComparisonOperatorsCodeGenerator(IncrementalGeneratorInit InitializeComparisonOperatorsCodeGenerator(context, comparables, options); } + private void InitializeEqualityComparisonOperatorsCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) + { + var comparables = validStates + .Select((state, _) => new EqualityComparisonOperatorsGeneratorState(state.Type, + state.KeyMember.Member, + state.Settings.EqualityComparisonOperators, + state.KeyMember.EqualityComparerAccessor is null ? null : new ComparerInfo(state.KeyMember.EqualityComparerAccessor, true))); + + InitializeEqualityComparisonOperatorsCodeGenerator(context, comparables, options); + } + private void InitializeAdditionOperatorsCodeGenerator(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider validStates, IncrementalValueProvider options) { var operators = validStates diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs index c4f7a2ec..9f3278e9 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/ValueObjects/ValueObjectSourceGeneratorState.cs @@ -9,6 +9,7 @@ public sealed class ValueObjectSourceGeneratorState : ITypeInformation, IEquatab public string TypeFullyQualifiedNullable { get; } public string TypeFullyQualifiedNullAnnotated => IsReferenceType ? TypeFullyQualifiedNullable : TypeFullyQualified; public string TypeMinimallyQualified { get; } + public bool IsEqualWithReferenceEquality => false; public string? Namespace { get; } public string Name { get; } @@ -108,7 +109,7 @@ public override bool Equals(object? obj) public bool Equals(ValueObjectSourceGeneratorState? other) { - if (ReferenceEquals(null, other)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs index e5b15a90..2994c80c 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs @@ -70,6 +70,11 @@ public static OperatorsGeneration FindComparisonOperators(this AttributeData att return GetOperatorsGeneration(attributeData, "ComparisonOperators"); } + public static OperatorsGeneration FindEqualityComparisonOperators(this AttributeData attributeData) + { + return GetOperatorsGeneration(attributeData, "EqualityComparisonOperators"); + } + public static bool? FindSkipToString(this AttributeData attributeData) { return GetBooleanParameterValue(attributeData, "SkipToString"); diff --git a/src/Thinktecture.Runtime.Extensions/EnumGenerationAttribute.cs b/src/Thinktecture.Runtime.Extensions/EnumGenerationAttribute.cs index 559c14d0..763bfa09 100644 --- a/src/Thinktecture.Runtime.Extensions/EnumGenerationAttribute.cs +++ b/src/Thinktecture.Runtime.Extensions/EnumGenerationAttribute.cs @@ -38,11 +38,31 @@ public string KeyPropertyName /// /// Indication whether and how the generator should generate the implementation of . /// + /// Please note that the comparison operators depend on . For example, if are set to + /// then the are set to as well. + /// /// This setting has no effect: - /// - if the key is not an itself and has no corresponding operators (op_GreaterThan, op_GreaterThanOrEqual, op_LessThan, op_LessThanOrEqual). + /// - if key-member is not an itself and has no corresponding operators (op_GreaterThan, op_GreaterThanOrEqual, op_LessThan, op_LessThanOrEqual). /// public OperatorsGeneration ComparisonOperators { get; set; } + private OperatorsGeneration _equalityComparisonOperators; + + /// + /// Indication whether and how the generator should generate the implementation of . + /// + /// Please note that the comparison operators depend on . For example, if are set to + /// then the are set to as well. + /// + /// This setting has no effect: + /// - if key-member is not an itself and has no corresponding operators (op_Equality, op_Inequality). + /// + public OperatorsGeneration EqualityComparisonOperators + { + get => ComparisonOperators > _equalityComparisonOperators ? ComparisonOperators : _equalityComparisonOperators; + set => _equalityComparisonOperators = value; + } + /// /// Indication whether the generator should skip the implementation of or not. /// diff --git a/src/Thinktecture.Runtime.Extensions/ValueObjectAttribute.cs b/src/Thinktecture.Runtime.Extensions/ValueObjectAttribute.cs index 1c07e31c..75781a06 100644 --- a/src/Thinktecture.Runtime.Extensions/ValueObjectAttribute.cs +++ b/src/Thinktecture.Runtime.Extensions/ValueObjectAttribute.cs @@ -106,12 +106,33 @@ public bool NullInFactoryMethodsYieldsNull /// /// Indication whether and how the generator should generate the implementation of . /// + /// Please note that the comparison operators depend on . For example, if are set to + /// then the are set to as well. + /// /// This setting has no effect: /// - on non-keyed value objects (i.e. has more than 1 field/property) /// - if key-member is not an itself and has no corresponding operators (op_GreaterThan, op_GreaterThanOrEqual, op_LessThan, op_LessThanOrEqual). /// public OperatorsGeneration ComparisonOperators { get; set; } + private OperatorsGeneration _equalityComparisonOperators; + + /// + /// Indication whether and how the generator should generate the implementation of . + /// + /// Please note that the comparison operators depend on . For example, if are set to + /// then the are set to as well. + /// + /// This setting has no effect: + /// - on non-keyed value objects (i.e. has more than 1 field/property) + /// - if key-member is not an itself and has no corresponding operators (op_Equality, op_Inequality). + /// + public OperatorsGeneration EqualityComparisonOperators + { + get => ComparisonOperators > _equalityComparisonOperators ? ComparisonOperators : _equalityComparisonOperators; + set => _equalityComparisonOperators = value; + } + /// /// Indication whether the generator should skip the implementation of or not. /// diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs index 08292849..871e4293 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs @@ -24,8 +24,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -161,28 +160,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -408,6 +385,194 @@ public static bool TryParse( } } +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARABLE_OPERATORS_CLASS = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestEnum : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + return global::System.Object.ReferenceEquals(obj, other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + return !(obj == other); + } +} + +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARABLE_OPERATORS_STRUCT = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial struct TestEnum : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestEnum obj, global::Thinktecture.Tests.TestEnum other) + { + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestEnum obj, global::Thinktecture.Tests.TestEnum other) + { + return !(obj == other); + } +} + +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARABLE_OPERATORS_CLASS_WITH_KEY_OVERLOADS = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestEnum : + global::System.Numerics.IEqualityOperators, + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + return global::System.Object.ReferenceEquals(obj, other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + return !(obj == other); + } + + private static bool Equals(global::Thinktecture.Tests.TestEnum? obj, int value) + { + if (obj is null) + return false; + + return KeyEqualityComparer.Equals(obj.Key, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestEnum? obj, int value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(int value, global::Thinktecture.Tests.TestEnum? obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestEnum? obj, int value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(int value, global::Thinktecture.Tests.TestEnum? obj) + { + return !(obj == value); + } +} + +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARABLE_OPERATORS_VALIDATABLE_CLASS = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestEnum : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestEnum? obj, global::Thinktecture.Tests.TestEnum? other) + { + return !(obj == other); + } +} + """; /* language=c# */ @@ -563,15 +728,17 @@ public partial class TestEnum : IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(mainOutput, _MAIN_OUTPUT_CLASS); AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); } [Fact] @@ -602,14 +769,16 @@ public partial class TestEnum : BaseClass, IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -618,8 +787,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -770,28 +938,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -966,15 +1112,17 @@ public partial class TestEnum : Thinktecture.IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(mainOutput, _MAIN_OUTPUT_CLASS); AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); } [Fact] @@ -992,19 +1140,19 @@ public partial class TestEnum : IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("TestEnum.EqualityComparisonOperators.g.cs")).Value; /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -1140,28 +1288,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::TestEnum? item1, global::TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::TestEnum? item1, global::TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::TestEnum? other) { @@ -1382,6 +1508,37 @@ public static bool TryParse( } } +"""); + + /* language=c# */ + AssertOutput(equalityComparisonOperators, _GENERATED_HEADER + """ + +partial class TestEnum : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::TestEnum? obj, global::TestEnum? other) + { + return global::System.Object.ReferenceEquals(obj, other); + } + + /// + /// Compares two instances of . + /// + /// Instance to compare. + /// Another instance to compare. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::TestEnum? obj, global::TestEnum? other) + { + return !(obj == other); + } +} + """); } @@ -1442,15 +1599,17 @@ public UnusedDerivedEnum(string key) """; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; var derivedTypesOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.DerivedTypes.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -1459,8 +1618,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -1596,28 +1754,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -1957,14 +2093,16 @@ public partial class TestEnum : IValidatableEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IValidatableEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_VALIDATABLE_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_VALIDATABLE_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -1973,8 +2111,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -2073,13 +2210,14 @@ private TestEnum(string key, bool isValid) private static global::Thinktecture.Tests.TestEnum CreateAndCheckInvalidItem(string key) { - var item = CreateInvalidItem(key); + var item = CreateInvalidItem(key); - if (item is null) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return 'null'."); + if (item is null) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return 'null'."); + + if (item.IsValid) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); - if (item.IsValid) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); return item; } @@ -2151,31 +2289,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - if (item1 is null) - return item2 is null; - - return item1.Equals(item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -2365,11 +2478,14 @@ namespace Thinktecture.Tests } "; var outputs = GetGeneratedOutputs(source, typeof(IValidatableEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_STRUCT); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -2379,8 +2495,7 @@ namespace Thinktecture.Tests [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Auto)] [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -2479,10 +2594,11 @@ private TestEnum(string key, bool isValid) private static global::Thinktecture.Tests.TestEnum CreateAndCheckInvalidItem(string key) { - var item = CreateInvalidItem(key); + var item = CreateInvalidItem(key); + + if (item.IsValid) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); - if (item.IsValid) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); return item; } @@ -2554,28 +2670,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum item1, global::Thinktecture.Tests.TestEnum item2) - { - return item1.Equals(item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum item1, global::Thinktecture.Tests.TestEnum item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum other) { @@ -2853,13 +2947,15 @@ public DerivedEnum( "; var outputs = GetGeneratedOutputs(source, typeof(IValidatableEnum<>).Assembly); - outputs.Should().HaveCount(3); + outputs.Should().HaveCount(4); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(parsableOutput, _PARSABLE_OUTPUT_VALIDATABLE_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_VALIDATABLE_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -2868,8 +2964,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -2974,16 +3069,17 @@ private TestEnum(string name, bool isValid, int structProperty, int? nullableStr private static global::Thinktecture.Tests.TestEnum CreateAndCheckInvalidItem(string name) { - var item = CreateInvalidItem(name); + var item = CreateInvalidItem(name); - if (item is null) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return 'null'."); + if (item is null) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return 'null'."); - if (item.IsValid) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); + if (item.IsValid) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must return an instance with property 'IsValid' equals to 'false'."); + + if (_itemsLookup.Value.ContainsKey(item.Name)) + throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return an instance with property 'Name' equals to one of a valid item."); - if (_itemsLookup.Value.ContainsKey(item.Name)) - throw new global::System.Exception("The implementation of method 'CreateInvalidItem' must not return an instance with property 'Name' equals to one of a valid item."); return item; } @@ -3050,31 +3146,6 @@ public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] st return global::Thinktecture.Tests.TestEnum.Get(name); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - if (item1 is null) - return item2 is null; - - return item1.Equals(item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -3297,17 +3368,19 @@ public partial class TestEnum : IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(5); + outputs.Should().HaveCount(6); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var formattable = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(formattable, _FORMATTABLE_OUTPUT_CLASS); AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_INT_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -3316,8 +3389,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -3441,28 +3513,6 @@ public static implicit operator int(global::Thinktecture.Tests.TestEnum? item) return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -3678,17 +3728,19 @@ public partial class TestEnum : IEnum } "; var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); - outputs.Should().HaveCount(5); + outputs.Should().HaveCount(6); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; var formattable = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; AssertOutput(formattable, _FORMATTABLE_OUTPUT_CLASS); AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_INT_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS_WITH_KEY_OVERLOADS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -3697,8 +3749,7 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestEnum : global::Thinktecture.IEnum, - global::System.IEquatable, - global::System.Numerics.IEqualityOperators + global::System.IEquatable { [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void ModuleInit() @@ -3822,28 +3873,6 @@ public static implicit operator int(global::Thinktecture.Tests.TestEnum? item) return global::Thinktecture.Tests.TestEnum.Get(key); } - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// true if items are equal; otherwise false. - public static bool operator ==(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return global::System.Object.ReferenceEquals(item1, item2); - } - - /// - /// Compares to instances of . - /// - /// Instance to compare. - /// Another instance to compare. - /// false if items are equal; otherwise true. - public static bool operator !=(global::Thinktecture.Tests.TestEnum? item1, global::Thinktecture.Tests.TestEnum? item2) - { - return !(item1 == item2); - } - /// public bool Equals(global::Thinktecture.Tests.TestEnum? other) { @@ -4003,7 +4032,8 @@ void AddItem(global::Thinktecture.Tests.TestEnum item, string itemName) namespace Thinktecture.Tests; partial class TestEnum : - global::System.Numerics.IComparisonOperators + global::System.Numerics.IComparisonOperators, + global::System.Numerics.IComparisonOperators { /// public static bool operator <(global::Thinktecture.Tests.TestEnum left, global::Thinktecture.Tests.TestEnum right) diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs index 433f77d3..040360bb 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/ValueObjectSourceGeneratorTests.cs @@ -447,6 +447,164 @@ partial class TestValueObject : } } +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARISON_OPERATORS_CLASS = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two 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) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two 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); + } +} + +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARISON_OPERATORS_STRUCT = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial struct TestValueObject : + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two 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 two 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); + } +} + +"""; + + /* language=c# */ + private const string _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS = _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.Numerics.IEqualityOperators, + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two 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) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two 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); + } + + private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + if (obj is null) + return false; + + return obj.StructField.Equals(value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(int value, global::Thinktecture.Tests.TestValueObject? obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, int value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(int value, global::Thinktecture.Tests.TestValueObject? obj) + { + return !(obj == value); + } +} + """; /* language=c# */ @@ -670,9 +828,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -890,9 +1045,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -1053,9 +1205,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -1225,9 +1374,6 @@ public bool Equals(global::TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -1307,16 +1453,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(formattableOutput, _FORMATTABLE_INT); AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -1324,7 +1472,6 @@ public partial class TestValueObject namespace Thinktecture.Tests { partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject { [global::System.Runtime.CompilerServices.ModuleInitializer] @@ -1388,31 +1535,6 @@ private TestValueObject(int 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) - { - if (obj is null) - return other is null; - - 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) { @@ -1425,9 +1547,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -1755,16 +1874,18 @@ public readonly partial struct TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -1773,7 +1894,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -1885,28 +2005,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) { @@ -1956,18 +2054,21 @@ public readonly partial struct TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); + /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -1975,7 +2076,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -2091,28 +2191,6 @@ private TestValueObject(int 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) { @@ -2379,18 +2457,21 @@ public readonly partial struct TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; var divisionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.DivisionOperators.g.cs")).Value; + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); + /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -2398,7 +2479,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -2514,28 +2594,6 @@ private TestValueObject(int 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) { @@ -2802,16 +2860,18 @@ public readonly partial struct TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -2820,7 +2880,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -2932,36 +2991,14 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) + /// + public override bool Equals(object? other) { - return obj.Equals(other); + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); } - /// - /// 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) + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject other) { return global::Thinktecture.ComparerAccessors.StringOrdinalIgnoreCase.EqualityComparer.Equals(this.ReferenceField, other.ReferenceField); } @@ -3003,16 +3040,18 @@ public readonly partial struct TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_STRUCT_STRING); AssertOutput(parsableOutput, _PARSABLE_STRUCT_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRUCT_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_STRUCT); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -3021,7 +3060,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial struct TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -3133,28 +3171,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) { @@ -3204,16 +3220,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -3222,7 +3240,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -3336,31 +3353,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -3373,9 +3365,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -3419,13 +3408,14 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; @@ -3435,6 +3425,7 @@ public partial class TestValueObject AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(parsableOutput, _PARSABLE_INT); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); @@ -3447,7 +3438,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -3565,31 +3555,6 @@ private TestValueObject(int 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) - { - if (obj is null) - return other is null; - - 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) { @@ -3602,9 +3567,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -3638,7 +3600,7 @@ public void Should_generate_class_with_DateOnly_key_member() namespace Thinktecture.Tests { - [ValueObject(ComparisonOperators = OperatorsGeneration.ForcedWithKeyTypeOverloads)] + [ValueObject] public partial class TestValueObject { public readonly DateOnly StructField; @@ -3646,13 +3608,14 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(5); + outputs.Should().HaveCount(6); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -3661,7 +3624,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -3779,8 +3741,188 @@ private TestValueObject(global::System.DateOnly structField) static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestValueObject obj && Equals(obj); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestValueObject? other) + { + if (other is null) + return false; + + if (global::System.Object.ReferenceEquals(this, other)) + return true; + + 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(); + } + } +} + +"""); + + /* language=c# */ + AssertOutput(formattableOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.IFormattable +{ + /// + public string ToString(string? format, global::System.IFormatProvider? formatProvider = null) + { + return this.StructField.ToString(format, formatProvider); + } +} + +"""); + + /* language=c# */ + AssertOutput(comparableOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.IComparable, + global::System.IComparable +{ + /// + public int CompareTo(object? obj) + { + if(obj is null) + return 1; + + if(obj is not global::Thinktecture.Tests.TestValueObject item) + throw new global::System.ArgumentException("Argument must be of type \"TestValueObject\".", nameof(obj)); + + return this.CompareTo(item); + } + + /// + public int CompareTo(global::Thinktecture.Tests.TestValueObject? obj) + { + if(obj is null) + return 1; + + return this.StructField.CompareTo(obj.StructField); + } +} + +"""); + + /* language=c# */ + AssertOutput(parsableOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.IParsable +{ + /// + public static global::Thinktecture.Tests.TestValueObject Parse(string s, global::System.IFormatProvider? provider) + { + var key = global::System.DateOnly.Parse(s, provider); + var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out var result); + + if(validationResult is null) + return result!; + + throw new global::System.FormatException(validationResult.ErrorMessage); + } + + /// + public static bool TryParse( + string? s, + global::System.IFormatProvider? provider, + [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestValueObject result) + { + if(s is null) + { + result = default; + return false; + } + + if(!global::System.DateOnly.TryParse(s, provider, out var key)) + { + result = default; + return false; + } + + var validationResult = global::Thinktecture.Tests.TestValueObject.Validate(key, out result!); + return validationResult is null; + } +} + +"""); + + /* language=c# */ + AssertOutput(comparisonOperatorsOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.Numerics.IComparisonOperators +{ + /// + public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField < right.StructField; + } + + /// + public static bool operator <=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField <= right.StructField; + } + + /// + public static bool operator >(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField > right.StructField; + } + + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } +} + +"""); + + /* language=c# */ + AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.Numerics.IEqualityOperators +{ /// - /// Compares to instances of . + /// Compares two instances of . /// /// Instance to compare. /// Another instance to compare. @@ -3794,7 +3936,7 @@ private TestValueObject(global::System.DateOnly structField) } /// - /// Compares to instances of . + /// Compares two instances of . /// /// Instance to compare. /// Another instance to compare. @@ -3803,6 +3945,161 @@ private TestValueObject(global::System.DateOnly structField) { return !(obj == other); } +} + +"""); + } + + [Fact] + public void Should_generate_class_with_DateOnly_key_member_with_DefaultWithKeyTypeOverloads() + { + /* language=c# */ + var source = @" +using System; +using Thinktecture; + +namespace Thinktecture.Tests +{ + [ValueObject(EqualityComparisonOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] + public partial class TestValueObject + { + public readonly DateOnly StructField; + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(6); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; + var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + + /* language=c# */ + AssertOutput(mainOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests +{ + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestValueObject : global::System.IEquatable, + global::Thinktecture.IKeyedValueObject, + global::Thinktecture.IKeyedValueObject + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + global::System.Func convertFromKey = new (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 type = typeof(global::Thinktecture.Tests.TestValueObject); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(type, typeof(global::System.DateOnly), false, false, convertFromKey, convertFromKeyExpression, convertFromKeyExpressionViaCtor, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(type, metadata); + } + + private static readonly global::System.Type _type = typeof(global::Thinktecture.Tests.TestValueObject); + + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate( + global::System.DateOnly structField, + 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; + } + + public static global::Thinktecture.Tests.TestValueObject Create(global::System.DateOnly structField) + { + var validationResult = Validate(structField, out global::Thinktecture.Tests.TestValueObject? obj); + + if (validationResult != global::System.ComponentModel.DataAnnotations.ValidationResult.Success) + throw new global::System.ComponentModel.DataAnnotations.ValidationException(validationResult!.ErrorMessage ?? "Validation failed."); + + return obj!; + } + + public static bool TryCreate( + global::System.DateOnly structField, + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Thinktecture.Tests.TestValueObject? obj) + { + var validationResult = Validate(structField, out obj); + + return validationResult == global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + + static partial void ValidateFactoryArguments(ref global::System.ComponentModel.DataAnnotations.ValidationResult? validationResult, ref global::System.DateOnly structField); + + partial void FactoryPostInit(); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + global::System.DateOnly global::Thinktecture.IKeyedValueObject.GetKey() + { + return this.StructField; + } + + /// + /// 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 global::System.DateOnly?(global::Thinktecture.Tests.TestValueObject? obj) + { + return obj?.StructField; + } + + /// + /// Explicit conversion to the type . + /// + /// Object to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("obj")] + public static explicit operator global::System.DateOnly(global::Thinktecture.Tests.TestValueObject obj) + { + if(obj is null) + throw new global::System.NullReferenceException(); + + return obj.StructField; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of . + public static explicit operator global::Thinktecture.Tests.TestValueObject(global::System.DateOnly structField) + { + return global::Thinktecture.Tests.TestValueObject.Create(structField); + } + + private TestValueObject(global::System.DateOnly structField) + { + ValidateConstructorArguments(ref structField); + + this.StructField = structField; + } + + static partial void ValidateConstructorArguments(ref global::System.DateOnly structField); /// public override bool Equals(object? other) @@ -3816,9 +4113,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -3968,13 +4262,102 @@ partial class TestValueObject : return left.StructField > right.StructField; } - /// - public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) - { - global::System.ArgumentNullException.ThrowIfNull(nameof(left)); - global::System.ArgumentNullException.ThrowIfNull(nameof(right)); - return left.StructField >= right.StructField; - } + /// + public static bool operator >=(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) + { + global::System.ArgumentNullException.ThrowIfNull(nameof(left)); + global::System.ArgumentNullException.ThrowIfNull(nameof(right)); + return left.StructField >= right.StructField; + } +} + +"""); + + /* language=c# */ + AssertOutput(equalityComparisonOperatorsOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests; + +partial class TestValueObject : + global::System.Numerics.IEqualityOperators, + global::System.Numerics.IEqualityOperators +{ + /// + /// Compares two 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) + { + if (obj is null) + return other is null; + + return obj.Equals(other); + } + + /// + /// Compares two 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); + } + + private static bool Equals(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + if (obj is null) + return false; + + return obj.StructField.Equals(value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// true if objects are equal; otherwise false. + public static bool operator ==(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) + { + return Equals(obj, value); + } + + /// + /// Compares an instance of with . + /// + /// Instance to compare. + /// Value to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::Thinktecture.Tests.TestValueObject? obj, global::System.DateOnly value) + { + return !(obj == value); + } + + /// + /// Compares an instance of with . + /// + /// Value to compare. + /// Instance to compare with. + /// false if objects are equal; otherwise true. + public static bool operator !=(global::System.DateOnly value, global::Thinktecture.Tests.TestValueObject? obj) + { + return !(obj == value); + } } """); @@ -4002,13 +4385,14 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; @@ -4017,6 +4401,7 @@ public partial class TestValueObject AssertOutput(formattableOutput, _FORMATTABLE_INT); AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(parsableOutput, _PARSABLE_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_INT_WITH_KEY_OVERLOADS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -4025,7 +4410,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -4143,31 +4527,6 @@ private TestValueObject(int 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) - { - if (obj is null) - return other is null; - - 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) { @@ -4180,9 +4539,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -4211,7 +4567,8 @@ public override string ToString() namespace Thinktecture.Tests; partial class TestValueObject : - global::System.Numerics.IComparisonOperators + global::System.Numerics.IComparisonOperators, + global::System.Numerics.IComparisonOperators { /// public static bool operator <(global::Thinktecture.Tests.TestValueObject left, global::Thinktecture.Tests.TestValueObject right) @@ -4547,16 +4904,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -4565,7 +4924,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -4680,31 +5038,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -4717,9 +5050,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -4763,16 +5093,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -4781,7 +5113,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -4894,31 +5225,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -4931,9 +5237,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -4977,13 +5280,14 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; @@ -4993,6 +5297,7 @@ public partial class TestValueObject AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(parsableOutput, _PARSABLE_INT); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); @@ -5005,7 +5310,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -5123,31 +5427,6 @@ private TestValueObject(int 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) - { - if (obj is null) - return other is null; - - 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) { @@ -5160,9 +5439,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -5204,13 +5480,14 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(9); + outputs.Should().HaveCount(10); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var formattableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Formattable.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; var additionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.AdditionOperators.g.cs")).Value; var subtractionOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.SubtractionOperators.g.cs")).Value; var multiplyOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.MultiplyOperators.g.cs")).Value; @@ -5220,6 +5497,7 @@ public partial class TestValueObject AssertOutput(comparableOutput, _COMPARABLE_INT); AssertOutput(parsableOutput, _PARSABLE_INT); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_INT); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); AssertOutput(additionOperatorsOutput, _ADDITION_OPERATORS_INT); AssertOutput(subtractionOperatorsOutput, _SUBTRACTION_OPERATORS_INT); AssertOutput(multiplyOperatorsOutput, _MULTIPLY_OPERATORS_INT); @@ -5232,7 +5510,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -5350,31 +5627,6 @@ private TestValueObject(int 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) - { - if (obj is null) - return other is null; - - 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) { @@ -5387,9 +5639,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -5434,16 +5683,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING); AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_CLASS_STRING); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -5452,7 +5703,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -5566,31 +5816,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -5603,9 +5828,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -5651,16 +5873,18 @@ public partial class TestValueObject } "; var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); - outputs.Should().HaveCount(4); + outputs.Should().HaveCount(5); var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Comparable.g.cs")).Value; var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.Parsable.g.cs")).Value; var comparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.ComparisonOperators.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; AssertOutput(comparableOutput, _COMPARABLE_CLASS_STRING_WITH_ORDINAL_COMPARER); AssertOutput(parsableOutput, _PARSABLE_CLASS_STRING); AssertOutput(comparisonOperatorsOutput, _COMPARISON_OPERATORS_STRING_WITH_ORDINAL_COMPARER); + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ AssertOutput(mainOutput, _GENERATED_HEADER + """ @@ -5669,7 +5893,6 @@ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -5783,31 +6006,6 @@ private TestValueObject(string referenceField) static partial void ValidateConstructorArguments(ref string referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -5820,9 +6018,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -5870,16 +6065,21 @@ public class Foo } } "; - var output = GetGeneratedOutput(source, typeof(ValueObjectAttribute).Assembly); + var outputs = GetGeneratedOutputs(source, typeof(ValueObjectAttribute).Assembly); + outputs.Should().HaveCount(2); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.g.cs")).Value; + var equalityComparisonOperatorsOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestValueObject.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(equalityComparisonOperatorsOutput, _EQUALITY_COMPARISON_OPERATORS_CLASS); /* language=c# */ - AssertOutput(output, _GENERATED_HEADER + """ + AssertOutput(mainOutput, _GENERATED_HEADER + """ namespace Thinktecture.Tests { [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] partial class TestValueObject : global::System.IEquatable, - global::System.Numerics.IEqualityOperators, global::Thinktecture.IKeyedValueObject, global::Thinktecture.IKeyedValueObject { @@ -5993,31 +6193,6 @@ private TestValueObject(global::Thinktecture.Tests.Foo referenceField) static partial void ValidateConstructorArguments(ref global::Thinktecture.Tests.Foo referenceField); - /// - /// 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) - { - if (obj is null) - return other is null; - - 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) { @@ -6030,9 +6205,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -6244,9 +6416,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true; @@ -6454,9 +6623,6 @@ public bool Equals(global::Thinktecture.Tests.TestValueObject? other) if (other is null) return false; - if (!global::System.Object.ReferenceEquals(GetType(), other.GetType())) - return false; - if (global::System.Object.ReferenceEquals(this, other)) return true;