Skip to content

Commit

Permalink
The generation of the equality operators can be controlled via "Equal…
Browse files Browse the repository at this point in the history
…ityComparisonOperators"
  • Loading branch information
PawelGerr committed Apr 1, 2023
1 parent 1a4e370 commit c5ff2ba
Show file tree
Hide file tree
Showing 32 changed files with 1,378 additions and 830 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Thinktecture.CodeAnalysis;

public readonly record struct ComparerInfo(string Comparer, bool IsAccessor);
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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(@"
/// <summary>
/// Compares two instances of <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""other"">Another instance to compare.</param>
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
public static bool operator ==(").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(@"
}
/// <summary>
/// Compares two instances of <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""other"">Another instance to compare.</param>
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
public static bool operator !=(").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(@";
}
/// <summary>
/// Compares an instance of <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/> with <see cref=""").Append(keyMember.TypeFullyQualified).Append(@"""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""value"">Value to compare with.</param>
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
public static bool operator ==(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value)
{
return Equals(obj, value);
}
/// <summary>
/// Compares an instance of <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/> with <see cref=""").Append(keyMember.TypeFullyQualified).Append(@"""/>.
/// </summary>
/// <param name=""value"">Value to compare.</param>
/// <param name=""obj"">Instance to compare with.</param>
/// <returns><c>true</c> if objects are equal; otherwise <c>false</c>.</returns>
public static bool operator ==(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj)
{
return Equals(obj, value);
}
/// <summary>
/// Compares an instance of <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/> with <see cref=""").Append(keyMember.TypeFullyQualified).Append(@"""/>.
/// </summary>
/// <param name=""obj"">Instance to compare.</param>
/// <param name=""value"">Value to compare with.</param>
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
public static bool operator !=(").Append(type.TypeFullyQualifiedNullAnnotated).Append(" obj, ").Append(keyMember.TypeFullyQualified).Append(@" value)
{
return !(obj == value);
}
/// <summary>
/// Compares an instance of <see cref=""").Append(keyMember.TypeFullyQualified).Append(@"""/> with <see cref=""").Append(type.TypeMinimallyQualified).Append(@"""/>.
/// </summary>
/// <param name=""value"">Value to compare.</param>
/// <param name=""obj"">Instance to compare with.</param>
/// <returns><c>false</c> if objects are equal; otherwise <c>true</c>.</returns>
public static bool operator !=(").Append(keyMember.TypeFullyQualified).Append(" value, ").Append(type.TypeFullyQualifiedNullAnnotated).Append(@" obj)
{
return !(obj == value);
}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Thinktecture.CodeAnalysis;

public readonly struct EqualityComparisonOperatorsGeneratorState : IEquatable<EqualityComparisonOperatorsGeneratorState>
{
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ public interface ITypeInformation : INamespaceAndName, ITypeFullyQualified
string TypeMinimallyQualified { get; }
string TypeFullyQualifiedNullAnnotated { get; }
bool IsReferenceType { get; }
bool IsEqualWithReferenceEquality { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public ConstructorState(IReadOnlyList<IMemberState> arguments)

public bool Equals(ConstructorState? other)
{
if (ReferenceEquals(null, other))
if (other is null)
return false;
if (ReferenceEquals(this, other))
return true;
Expand All @@ -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;
Expand Down
Loading

0 comments on commit c5ff2ba

Please sign in to comment.