Skip to content

Commit 9807ab1

Browse files
authored
fix: honor OverloadResolutionPriority on net8 consumers (#6276, #6280) (#6282)
* fix: honor OverloadResolutionPriority on net8 consumers (#6276, #6280) TUnit's Assert.That(...) overloads are disambiguated with [OverloadResolutionPriority], which is only honored by C# 13+. Projects targeting net8.0 (and earlier) default to C# 12, which silently ignores the attribute, so Assert.That binds to the generic catch-all overload instead of the typed collection/dictionary overload. The typed surface (All, Count, ContainsKey, ...) then disappears and calls fail to compile (CS0411/CS0121). net9.0/net10.0 default to C# 13/14, so they were unaffected. PR #2437 already set <LangVersion>latest</LangVersion> to address this, but it lived in the package .targets. The SDK assigns the target-framework default LangVersion (e.g. 12.0 for net8.0) during the .targets phase, so a .targets default guarded by Condition="'$(LangVersion)' == ''" never fires. Move the default to the package .props (imported earlier, before the SDK default) so it actually wins, while still letting consumers override LangVersion explicitly. Also remove the #if NET9_0_OR_GREATER gate from ImplicitConversionEqualityExtensions (a partial band-aid for the same root cause, issue #5765) and the matching test gates, restoring the implicit-conversion IsEqualTo / collection-overload-resolution coverage on net8.0. The JsonElement.DeepEquals gate stays (genuine net9+ BCL API), as do the Mocks ref-struct (allows ref struct) gates. Validated end to end with a packed net8 consumer: forced C#12 reproduces CS0411; the props default builds clean. net8 test build green (Issue5720 15/15, CollectionOverloadResolution 47/47). * test: update net8 PublicAPI snapshot for restored typed Assert.That overloads Dropping the #5765 #if NET9 gate restored IsEqualTo<TValue,TOther> and IsNotEqualTo<TValue,TOther> on net8, changing the net8 public API surface.
1 parent 1b7218a commit 9807ab1

9 files changed

Lines changed: 49 additions & 18 deletions

File tree

TUnit.Assertions.Tests/Bugs/Issue5720Tests.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// Gated to match ImplicitConversionEqualityExtensions.cs — see issue #5765.
2-
#if NET9_0_OR_GREATER
31
namespace TUnit.Assertions.Tests.Bugs;
42

53
/// <summary>
@@ -193,5 +191,3 @@ await Assert.That(assertionException.InnerException!.Message).StartsWith(
193191
$"No implicit conversion operator from '{typeof(UnrelatedType)}' to '{typeof(string)}' was found.");
194192
}
195193
}
196-
197-
#endif

TUnit.Assertions.Tests/CollectionOverloadResolutionTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#if NET9_0_OR_GREATER
21
using System.Collections;
32
using System.Collections.Concurrent;
43
using System.Collections.Frozen;
@@ -393,4 +392,3 @@ public async Task NullableIReadOnlyCollection_ResolvesToCollectionAssertion()
393392
await Assert.That(collection).Contains(1);
394393
}
395394
}
396-
#endif

TUnit.Assertions/Extensions/ImplicitConversionEqualityExtensions.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
// Gated to .NET 9+ because these overloads rely on [OverloadResolutionPriority] to
2-
// lose to the source-generated single-generic IsEqualTo / IsNotEqualTo on same-type
3-
// calls, and that attribute is silently dropped on net8.0 / netstandard2.0 (Polyfill
4-
// does not supply it), causing CS0121. See issue #5765.
5-
#if NET9_0_OR_GREATER
1+
// These overloads rely on [OverloadResolutionPriority] (honored only by C# 13+) to lose to the
2+
// source-generated same-type IsEqualTo / IsNotEqualTo. TUnit raises consumer LangVersion so the
3+
// attribute is honored on every target (see TUnit.Assertions.props). Issues #5765, #6276, #6280.
64
using System.Collections.Concurrent;
75
using System.Diagnostics.CodeAnalysis;
86
using System.Reflection;
@@ -151,4 +149,3 @@ internal static class ImplicitConversionCache
151149
return null;
152150
}
153151
}
154-
#endif

TUnit.Assertions/TUnit.Assertions.props

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33

4+
<!--
5+
TUnit's Assert.That(...) overloads are disambiguated with [OverloadResolutionPriority],
6+
which only C# 13+ honors. net8.0-and-earlier projects default to C# 12 and silently ignore
7+
it, breaking overload resolution (CS0411/CS0121; issues #6276, #6280). This default MUST
8+
live in .props, not .targets: the SDK sets the target-framework default LangVersion during
9+
the .targets phase, so a .targets default (Condition="'$(LangVersion)' == ''") never wins.
10+
Consumers can still override LangVersion explicitly.
11+
-->
12+
<PropertyGroup>
13+
<LangVersion Condition="'$(LangVersion)' == ''">latest</LangVersion>
14+
</PropertyGroup>
15+
416
<PropertyGroup Condition="'$(TUnitAssertionsImplicitUsings)' == ''">
517
<TUnitAssertionsImplicitUsings>true</TUnitAssertionsImplicitUsings>
618
</PropertyGroup>

TUnit.Engine/TUnit.Engine.props

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
<TUnitReflectionScanner Condition="'$(IsVBProject)' == 'true' or '$(IsFSharpProject)' == 'true'">true</TUnitReflectionScanner>
2121
</PropertyGroup>
2222

23+
<!--
24+
TUnit's Assert.That(...) overloads are disambiguated with [OverloadResolutionPriority],
25+
which only C# 13+ honors. net8.0-and-earlier projects default to C# 12 and silently ignore
26+
it, breaking overload resolution (CS0411/CS0121; issues #6276, #6280). This default MUST
27+
live in .props, not .targets: the SDK sets the target-framework default LangVersion during
28+
the .targets phase, so a .targets default (Condition="'$(LangVersion)' == ''") never wins.
29+
Consumers can still override LangVersion explicitly.
30+
-->
31+
<PropertyGroup>
32+
<LangVersion Condition="'$(LangVersion)' == ''">latest</LangVersion>
33+
</PropertyGroup>
34+
2335
<ItemGroup>
2436
<!--
2537
!!! IMPORTANT !!!

TUnit.Engine/TUnit.Engine.targets

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33

4-
<PropertyGroup>
5-
<LangVersion Condition="'$(LangVersion)' == ''">latest</LangVersion>
6-
</PropertyGroup>
4+
<!-- LangVersion default moved to TUnit.Engine.props: a .targets default never wins against
5+
the SDK's target-framework LangVersion default (set during the .targets phase). See #6276/#6280. -->
76

87
<ItemGroup Condition="'$(TUnitImplicitUsings)' == 'true'">
98
<Using Include="TUnit.Core" />

TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4299,6 +4299,9 @@ namespace .Extensions
42994299
{
43004300
public static .<TValue> IsEqualTo<TValue>(this .<TValue> source, TValue? expected, [.("expected")] string? expectedExpression = null) { }
43014301
public static .<TValue> IsEqualTo<TValue>(this .<TValue> source, TValue? expected, .<TValue> comparer, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null) { }
4302+
[.("Looks up implicit conversion operators via reflection. Trimming may remove user-d" +
4303+
"efined operators.")]
4304+
public static .<TOther> IsEqualTo<TValue, TOther>(this .<TValue> source, TOther? expected, [.("expected")] string? expectedExpression = null) { }
43024305
}
43034306
public static class EquatableAssertionExtensions
43044307
{
@@ -5225,6 +5228,9 @@ namespace .Extensions
52255228
public static class NotEqualsAssertionExtensions
52265229
{
52275230
public static .<TValue> IsNotEqualTo<TValue>(this .<TValue> source, TValue notExpected, .<TValue>? comparer = null, [.("notExpected")] string? notExpectedExpression = null, [.("comparer")] string? comparerExpression = null) { }
5231+
[.("Looks up implicit conversion operators via reflection. Trimming may remove user-d" +
5232+
"efined operators.")]
5233+
public static .<TOther> IsNotEqualTo<TValue, TOther>(this .<TValue> source, TOther? notExpected, [.("notExpected")] string? notExpectedExpression = null) { }
52285234
}
52295235
public static class NotEquivalentToAssertionExtensions
52305236
{

TUnit/TUnit.props

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@
1717
<TUnitAssertionsImplicitUsings Condition="'$(TUnitAssertionsImplicitUsings)' == ''">true</TUnitAssertionsImplicitUsings>
1818
</PropertyGroup>
1919

20+
<!--
21+
TUnit's Assert.That(...) overloads are disambiguated with [OverloadResolutionPriority],
22+
which only C# 13+ honors. net8.0-and-earlier projects default to C# 12 and silently ignore
23+
it, breaking overload resolution (CS0411/CS0121; issues #6276, #6280). This default MUST
24+
live in .props, not .targets: the SDK sets the target-framework default LangVersion during
25+
the .targets phase, so a .targets default (Condition="'$(LangVersion)' == ''") never wins.
26+
Consumers can still override LangVersion explicitly.
27+
-->
28+
<PropertyGroup>
29+
<LangVersion Condition="'$(LangVersion)' == ''">latest</LangVersion>
30+
</PropertyGroup>
31+
2032
<ItemGroup>
2133
<!--
2234
!!! IMPORTANT !!!

TUnit/TUnit.targets

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33

4-
<PropertyGroup>
5-
<LangVersion Condition="'$(LangVersion)' == ''">latest</LangVersion>
6-
</PropertyGroup>
4+
<!-- LangVersion default moved to TUnit.props: a .targets default never wins against the SDK's
5+
target-framework LangVersion default (set during the .targets phase). See #6276/#6280. -->
76

87
</Project>

0 commit comments

Comments
 (0)