feat: support Is<T>/IsExactly<T> in generic contexts via IThatSubject#942
Conversation
The typed Is<TType>/IsNot<TType>/IsExactly<TType>/IsNotExactly<TType> extensions only matched IThat<object?>, so they failed to resolve inside generic helpers where T is unconstrained (covariance can't apply when T might be a struct). Adding them as instance members on a new IThatSubject<T> : IThat<T> interface fixes resolution: T is fixed by the receiver, leaving only TType for the caller to specify. Expect.That overloads for T, T[]?, Task<T>, ValueTask<T> now return IThatSubject<T>; span overloads keep IThat<SpanWrapper<T>>. The old generic extensions on IThat<object?> are removed (shadowed by the interface methods); the Type-parameter overloads stay as extensions.
There was a problem hiding this comment.
Pull request overview
This PR addresses a C# generic type inference/resolution limitation that prevented using Is<TType>/IsNot<TType>/IsExactly<TType>/IsNotExactly<TType> when the subject type is an unconstrained type parameter T. It introduces IThatSubject<T> so the receiver fixes T, leaving only TType explicit for the caller.
Changes:
- Introduces
IThatSubject<T>and implements theIs*/IsNot*type-check APIs as instance members onThatSubject<T>to make them callable in unconstrained generic contexts. - Updates
Expect.That(...)overloads forT,T[]?,Task<T>, andValueTask<T>to returnIThatSubject<T>(span overloads remainIThat<SpanWrapper<T>>). - Removes the old generic
ThatObject.Is<TType>/IsNot<TType>/IsExactly<TType>/IsNotExactly<TType>extensions onIThat<object?>and adds tests covering generic helper scenarios.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/aweXpect.Tests/Objects/ThatObject.IsNotExactly.Tests.cs | Adds coverage for IsNotExactly<TType> inside unconstrained generic helpers. |
| Tests/aweXpect.Tests/Objects/ThatObject.IsNot.Tests.cs | Adds coverage for IsNot<TType> inside unconstrained generic helpers. |
| Tests/aweXpect.Tests/Objects/ThatObject.IsExactly.Tests.cs | Adds coverage for IsExactly<TType> inside unconstrained generic helpers. |
| Tests/aweXpect.Tests/Objects/ThatObject.Is.Tests.cs | Adds coverage for Is<TType> inside unconstrained generic helpers. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt | Updates core API snapshot for IThatSubject<T> and updated Expect.That return types. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt | Updates core API snapshot for IThatSubject<T> and updated Expect.That return types. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net10.0.txt | Updates core API snapshot for IThatSubject<T> and updated Expect.That return types. |
| Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt | Updates main API snapshot to remove the old generic ThatObject.Is*/IsNot* extensions. |
| Tests/aweXpect.Api.Tests/Expected/aweXpect_net10.0.txt | Updates main API snapshot to remove the old generic ThatObject.Is*/IsNot* extensions. |
| Source/aweXpect/That/Objects/ThatObject.IsExactly.cs | Removes generic IsExactly<TType>/IsNotExactly<TType> extensions on IThat<object?> (type-parameter overloads remain). |
| Source/aweXpect/That/Objects/ThatObject.Is.cs | Removes generic Is<TType>/IsNot<TType> extensions on IThat<object?> (type-parameter overloads remain). |
| Source/aweXpect.Core/Expect.cs | Changes Expect.That overloads to return IThatSubject<T> for better generic-context resolution. |
| Source/aweXpect.Core/Core/ThatSubject.cs | Implements IThatSubject<T> and adds instance Is*/IsNot* members that add the new constraints. |
| Source/aweXpect.Core/Core/IThatSubject.cs | Adds the new IThatSubject<T> interface defining instance type-check methods. |
| Source/aweXpect.Core/Core/Constraints/IsOfTypeConstraint.cs | Adds reusable core constraint for actual is TType checks. |
| Source/aweXpect.Core/Core/Constraints/IsExactlyOfTypeConstraint.cs | Adds reusable core constraint for exact runtime type equality checks. |
| Pipeline/Build.cs | Changes default BuildScope to CoreOnly (affects CI scope). |
Test Results 7 files - 43 7 suites - 43 44s ⏱️ - 7m 22s Results for commit fbc0998. ± Comparison against base commit 44a5e72. This pull request removes 18402 and adds 101 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
The Is/IsNot/IsExactly/IsNotExactly methods returned results whose And/Or continuation was IThat<T>. Since the generic extensions on IThat<object?> were removed, that broke chains like That(x).Is<string>().And.IsNot<int>(): the continuation didn't expose the typed instance methods. Returning IThatSubject<T> as the continuation keeps the methods reachable, while remaining assignable to IThat<T> for any IThat<T> extensions further down the chain.
Match the existing convention so test methods appear in the file in the same order as the test explorer.
Is<T>/IsExactly<T> in generic contexts via IThatSubject
The .editorconfig had csharp_style_expression_bodied_local_functions set to false, conflicting with the solution DotSettings (ExpressionBody) and with how local functions are actually written across the codebase. ReSharper cleanup follows .editorconfig, so it was rewriting expression bodies to block bodies on save. Aligning .editorconfig to true keeps existing expression-bodied local functions intact.
|
|
This is addressed in release v2.32.0. |


The typed Is/IsNot/IsExactly/IsNotExactly extensions only matched IThat<object?>, so they failed to resolve inside generic helpers where T is unconstrained (covariance can't apply when T might be a struct). Adding them as instance members on a new IThatSubject : IThat interface fixes resolution: T is fixed by the receiver, leaving only TType for the caller to specify.
Expect.That overloads for T, T[]?, Task, ValueTask now return IThatSubject; span overloads keep IThat<SpanWrapper>. The old generic extensions on IThat<object?> are removed (shadowed by the interface methods); the Type-parameter overloads stay as extensions.
Isfor generic types not working #726