Skip to content

Commit

Permalink
Alternative approach
Browse files Browse the repository at this point in the history
To prevent calling overloads using implicit operators we need a generic fall back.

As we can only have 1 generic method we have to write out the Numeric
overload into its 11 separate types.
  • Loading branch information
manfred-brands committed Dec 18, 2024
1 parent 36b8b1d commit a12d6ab
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 91 deletions.
95 changes: 89 additions & 6 deletions src/NUnitFramework/framework/Constraints/ConstraintExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public Constraint Matches<TActual>(Predicate<TActual> predicate)
/// <summary>
/// Returns a constraint that tests two items for equality
/// </summary>
public EqualConstraint EqualTo(object? expected)
public EqualConstraint EqualTo<T>(T? expected)

This comment has been minimized.

Copy link
@smdn

smdn Dec 18, 2024

I think it could affect the behavior of NUnit.Analyzers by Is.EqualTo(object) no longer existing.

{
return Append(new EqualConstraint(expected));
}
Expand Down Expand Up @@ -462,13 +462,96 @@ public EqualTimeBaseConstraint<TimeSpan> EqualTo(TimeSpan expected)
/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
#pragma warning disable CS3024 // Constraint type is not CLS-compliant
public EqualNumericConstraint<T> EqualTo<T>(T expected)
where T : unmanaged, IConvertible, IEquatable<T>
public EqualNumericConstraint<double> EqualTo(double expected)
{
return Append(new EqualNumericConstraint<T>(expected));
return Append(new EqualNumericConstraint<double>(expected));
}
#pragma warning restore CS3024 // Constraint type is not CLS-compliant

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<float> EqualTo(float expected)
{
return Append(new EqualNumericConstraint<float>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<decimal> EqualTo(decimal expected)
{
return Append(new EqualNumericConstraint<decimal>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<long> EqualTo(long expected)
{
return Append(new EqualNumericConstraint<long>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<int> EqualTo(int expected)
{
return Append(new EqualNumericConstraint<int>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<short> EqualTo(short expected)
{
return Append(new EqualNumericConstraint<short>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<byte> EqualTo(byte expected)
{
return Append(new EqualNumericConstraint<byte>(expected));
}

#pragma warning disable CS3002 // Return type is not CLS-compliant
#pragma warning disable CS3001 // Argument type is not CLS-compliant

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<ulong> EqualTo(ulong expected)
{
return Append(new EqualNumericConstraint<ulong>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<uint> EqualTo(uint expected)
{
return Append(new EqualNumericConstraint<uint>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<ushort> EqualTo(ushort expected)
{
return Append(new EqualNumericConstraint<ushort>(expected));
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public EqualNumericConstraint<sbyte> EqualTo(sbyte expected)
{
return Append(new EqualNumericConstraint<sbyte>(expected));
}

#pragma warning restore CS3001 // Argument type is not CLS-compliant
#pragma warning restore CS3002 // Return type is not CLS-compliant

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ public class EqualNumericConstraint<T> : EqualNumericWithoutUsingConstraint<T>,
/// <summary>
/// Initializes a new instance of the <see cref="EqualConstraint"/> class.
/// </summary>
/// <remarks>
/// Marked internal to prevent external instantiation with non-supported types.
/// </remarks>
/// <param name="expected">The expected value.</param>
public EqualNumericConstraint(T expected)
internal EqualNumericConstraint(T expected)
: base(expected)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ public class EqualNumericWithoutUsingConstraint<T> : Constraint
/// <summary>
/// Initializes a new instance of the <see cref="EqualConstraint"/> class.
/// </summary>
/// <remarks>
/// Marked internal to prevent external instantiation with non-supported types.
/// </remarks>
/// <param name="expected">The expected value.</param>
public EqualNumericWithoutUsingConstraint(T expected)
internal EqualNumericWithoutUsingConstraint(T expected)
: base(expected)
{
_expected = expected;
Expand Down Expand Up @@ -123,21 +126,7 @@ public EqualNumericWithoutUsingConstraint<T> Percent
/// <returns>True for success, false for failure</returns>
public ConstraintResult ApplyTo(T actual)
{
// As we cannot use exact generic constraints, T could be a type not supported by Numerics.
// In that case fall back to the default equality comparison.
bool hasSucceeded;

if (Numerics.IsNumericType(typeof(T)))
{
hasSucceeded = Numerics.AreEqual(_expected, actual, ref _tolerance);
}
else
{
if (!_tolerance.IsUnsetOrDefault)
throw new InvalidOperationException("Cannot use Tolerance with IEquatable<>.");

hasSucceeded = _expected.Equals(actual);
}
bool hasSucceeded = Numerics.AreEqual(_expected, actual, ref _tolerance);

return ConstraintResult(actual, hasSucceeded);
}
Expand All @@ -155,7 +144,7 @@ public override ConstraintResult ApplyTo<TActual>(TActual actual)
{
hasSucceeded = false;
}
else if (actual is T t && Numerics.IsNumericType(typeof(T)))
else if (actual is T t)
{
hasSucceeded = Numerics.AreEqual(_expected, t, ref _tolerance);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Linq;
using System.Text;
using NUnit.Framework.Constraints.Comparers;

Expand Down Expand Up @@ -129,10 +128,6 @@ public sealed override ConstraintResult ApplyTo<TActual>(TActual actual)
{
return ApplyTo(actualString);
}
else if (CanCastToString(actual, out string? actualCastToString))
{
return ApplyTo(actualCastToString);
}
else if (actual is IEquatable<string> equatableString)
{
if (_caseInsensitive || _ignoringWhiteSpace)
Expand All @@ -157,26 +152,6 @@ public sealed override ConstraintResult ApplyTo<TActual>(TActual actual)
return ConstraintResult(actual, hasSucceeded);
}

private static bool CanCastToString<TActual>(TActual actual, out string? s)
{
// Check if the type implements an implicit cast to string
// Note that we don't have to check the parameter types
// as the compiler only allows cast from/to TActual and we check the return type.
var implicitCastToString = typeof(TActual).GetMethods()
.FirstOrDefault(x => x.IsStatic &&
x.Name == "op_Implicit" &&
x.ReturnType == typeof(string));

if (implicitCastToString is null)
{
s = null;
return false;
}

s = (string?)implicitCastToString.Invoke(null, [actual]);
return true;
}

private ConstraintResult ConstraintResult<T>(T actual, bool hasSucceeded)
{
return new EqualConstraintResult(this, actual, _caseInsensitive, _ignoringWhiteSpace, _clipStrings, hasSucceeded);
Expand Down
95 changes: 89 additions & 6 deletions src/NUnitFramework/framework/Is.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public abstract class Is
/// <summary>
/// Returns a constraint that tests two items for equality
/// </summary>
public static EqualConstraint EqualTo(object? expected)
public static EqualConstraint EqualTo<T>(T? expected)
{
return new EqualConstraint(expected);
}
Expand Down Expand Up @@ -193,13 +193,96 @@ public static EqualTimeBaseConstraint<TimeSpan> EqualTo(TimeSpan expected)
/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
#pragma warning disable CS3024 // Constraint type is not CLS-compliant
public static EqualNumericConstraint<T> EqualTo<T>(T expected)
where T : unmanaged, IConvertible, IEquatable<T>
public static EqualNumericConstraint<double> EqualTo(double expected)
{
return new EqualNumericConstraint<T>(expected);
return new EqualNumericConstraint<double>(expected);
}
#pragma warning restore CS3024 // Constraint type is not CLS-compliant

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<float> EqualTo(float expected)
{
return new EqualNumericConstraint<float>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<decimal> EqualTo(decimal expected)
{
return new EqualNumericConstraint<decimal>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<long> EqualTo(long expected)
{
return new EqualNumericConstraint<long>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<int> EqualTo(int expected)
{
return new EqualNumericConstraint<int>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<short> EqualTo(short expected)
{
return new EqualNumericConstraint<short>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<byte> EqualTo(byte expected)
{
return new EqualNumericConstraint<byte>(expected);
}

#pragma warning disable CS3002 // Return type is not CLS-compliant
#pragma warning disable CS3001 // Argument type is not CLS-compliant

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<ulong> EqualTo(ulong expected)
{
return new EqualNumericConstraint<ulong>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<uint> EqualTo(uint expected)
{
return new EqualNumericConstraint<uint>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<ushort> EqualTo(ushort expected)
{
return new EqualNumericConstraint<ushort>(expected);
}

/// <summary>
/// Returns a constraint that tests two numbers for equality
/// </summary>
public static EqualNumericConstraint<sbyte> EqualTo(sbyte expected)
{
return new EqualNumericConstraint<sbyte>(expected);
}

#pragma warning restore CS3001 // Argument type is not CLS-compliant
#pragma warning restore CS3002 // Return type is not CLS-compliant

#endregion

Expand Down
Loading

0 comments on commit a12d6ab

Please sign in to comment.