Description
After upgrading from 1.34.5 → 1.40.0, every IsEqualTo(...) call site where the expected value has the same type as the asserted value now fails to compile with CS0121 — "The call is ambiguous".
The regression was introduced by #5751 — feat(assertions): IsEqualTo with implicitly-convertible wrappers, which added a second overload alongside the existing one:
public static InvokableValueAssertionBuilder<TValue> IsEqualTo<TValue, TOther>(
this IAssertionSource<TValue> source,
TOther? expected,
string? because = null);
public static InvokableValueAssertionBuilder<TValue> IsEqualTo<TValue>(
this IAssertionSource<TValue> source,
TValue? expected,
string? because = null);
When expected has the same type as TValue, both overloads are equally applicable and C# overload resolution can't pick a winner — TOther = TValue satisfies the two-generic version exactly as well as the single-generic one binds.
This is a source-breaking change affecting effectively every existing test suite that asserts on enums, primitives, value types, records, etc. The 1.40.0 release notes don't flag it as breaking.
IsEqualTo ambiguity has been a recurring class of regression — prior history worth referencing:
Expected Behavior
IsEqualTo(expected) should compile and bind unambiguously when expected is the same type as the value under assertion (the overwhelmingly common case), as it did on 1.39.0 and all earlier versions.
Actual Behavior
The compiler reports CS0121 and the build fails:
error CS0121: The call is ambiguous between the following methods or properties:
'TUnit.Assertions.Extensions.EqualsAssertionExtensions.IsEqualTo<TValue, TOther>(IAssertionSource<TValue>, TOther?, string?)'
and
'TUnit.Assertions.Extensions.EqualsAssertionExtensions.IsEqualTo<TValue>(IAssertionSource<TValue>, TValue?, string?)'
Steps to Reproduce
-
Create a new TUnit test project targeting any current .NET TFM (8/9/10).
-
Reference TUnit 1.40.0.
-
Add a test that asserts equality where the expected value has the same exact type as the asserted value:
using System.Net;
using TUnit.Assertions;
using TUnit.Assertions.Extensions;
public class Repro
{
[Test]
public async Task IsEqualTo_Same_Type_Is_Ambiguous()
{
using var http = new HttpClient();
var response = await http.GetAsync("https://example.com/");
// CS0121 on 1.40.0 — compiled cleanly on 1.39.0 and earlier.
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
}
}
-
Run dotnet build — observe the CS0121 error.
Workaround: specify the generic argument explicitly:
await Assert.That(response.StatusCode).IsEqualTo<HttpStatusCode>(HttpStatusCode.OK);
TUnit Version
1.40.0
.NET Version
.NET 8.0 / .NET 9.0 / .NET 10.0 (reproduces on all three TFMs)
Operating System
Linux
IDE / Test Runner
dotnet CLI (dotnet test / dotnet run)
Error Output / Stack Trace
error CS0121: The call is ambiguous between the following methods or properties:
'TUnit.Assertions.Extensions.EqualsAssertionExtensions.IsEqualTo<TValue, TOther>(TUnit.Assertions.Core.IAssertionSource<TValue>, TOther?, string?)'
and
'TUnit.Assertions.Extensions.EqualsAssertionExtensions.IsEqualTo<TValue>(TUnit.Assertions.Core.IAssertionSource<TValue>, TValue?, string?)'
Additional Context
- Not using AOT or trimming.
- No custom configuration; stock TUnit setup.
- Reproduces deterministically on every same-type
IsEqualTo call (independent of whether the type is an enum, primitive, value type, or record).
Suggested fix — disambiguate at the API level so callers don't have to:
- Constrain the new overload so it only kicks in when
TOther is not TValue — e.g. by using a distinct method name (IsEqualToConverted, IsEquivalentValue, ...) or a different parameter shape.
- Merge the two overloads — keep only
IsEqualTo<TValue>(TValue?) and rely on the language's existing implicit-conversion behaviour, which already binds anything implicitly convertible to TValue to that overload.
- Disambiguate by receiver type — make the two-generic overload an extension on a more specialised receiver so overload resolution is decided by the receiver, not by the argument.
Option 2 is the cleanest — the existing TValue overload already accepts implicitly-convertible values; the new overload only adds value for cases the language won't implicitly convert, which would be more clearly served by a distinct method name.
IDE-Specific Issue?
Description
After upgrading from 1.34.5 → 1.40.0, every
IsEqualTo(...)call site where the expected value has the same type as the asserted value now fails to compile with CS0121 — "The call is ambiguous".The regression was introduced by #5751 —
feat(assertions): IsEqualTo with implicitly-convertible wrappers, which added a second overload alongside the existing one:When
expectedhas the same type asTValue, both overloads are equally applicable and C# overload resolution can't pick a winner —TOther = TValuesatisfies the two-generic version exactly as well as the single-generic one binds.This is a source-breaking change affecting effectively every existing test suite that asserts on enums, primitives, value types, records, etc. The 1.40.0 release notes don't flag it as breaking.
IsEqualToambiguity has been a recurring class of regression — prior history worth referencing:new()(closed Nov 2025)TUnit.Assertions.Extensions.Generic(closed Oct 2024).IsEqualTo(...)(the original ambiguity fix, Oct 2024)Expected Behavior
IsEqualTo(expected)should compile and bind unambiguously whenexpectedis the same type as the value under assertion (the overwhelmingly common case), as it did on 1.39.0 and all earlier versions.Actual Behavior
The compiler reports CS0121 and the build fails:
Steps to Reproduce
Create a new TUnit test project targeting any current .NET TFM (8/9/10).
Reference
TUnit1.40.0.Add a test that asserts equality where the expected value has the same exact type as the asserted value:
Run
dotnet build— observe the CS0121 error.Workaround: specify the generic argument explicitly:
TUnit Version
1.40.0
.NET Version
.NET 8.0 / .NET 9.0 / .NET 10.0 (reproduces on all three TFMs)
Operating System
Linux
IDE / Test Runner
dotnet CLI (dotnet test / dotnet run)
Error Output / Stack Trace
Additional Context
IsEqualTocall (independent of whether the type is an enum, primitive, value type, or record).Suggested fix — disambiguate at the API level so callers don't have to:
TOtheris notTValue— e.g. by using a distinct method name (IsEqualToConverted,IsEquivalentValue, ...) or a different parameter shape.IsEqualTo<TValue>(TValue?)and rely on the language's existing implicit-conversion behaviour, which already binds anything implicitly convertible toTValueto that overload.Option 2 is the cleanest — the existing
TValueoverload already accepts implicitly-convertible values; the new overload only adds value for cases the language won't implicitly convert, which would be more clearly served by a distinct method name.IDE-Specific Issue?
dotnet testordotnet run, not just in my IDE