Skip to content

feat: support Is<T>/IsExactly<T> in generic contexts via IThatSubject#942

Merged
vbreuss merged 4 commits into
mainfrom
topic/enable-is-isexactly
May 10, 2026
Merged

feat: support Is<T>/IsExactly<T> in generic contexts via IThatSubject#942
vbreuss merged 4 commits into
mainfrom
topic/enable-is-isexactly

Conversation

@vbreuss
Copy link
Copy Markdown
Member

@vbreuss vbreuss commented May 10, 2026

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.


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.
@vbreuss vbreuss self-assigned this May 10, 2026
Copilot AI review requested due to automatic review settings May 10, 2026 08:42
@vbreuss vbreuss added enhancement New feature or request breaking change The changes require a new major version labels May 10, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the Is*/IsNot* type-check APIs as instance members on ThatSubject<T> to make them callable in unconstrained generic contexts.
  • Updates Expect.That(...) overloads for T, T[]?, Task<T>, and ValueTask<T> to return IThatSubject<T> (span overloads remain IThat<SpanWrapper<T>>).
  • Removes the old generic ThatObject.Is<TType>/IsNot<TType>/IsExactly<TType>/IsNotExactly<TType> extensions on IThat<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).

Comment thread Pipeline/Build.cs
Comment thread Source/aweXpect.Core/Core/ThatSubject.cs Outdated
Comment thread Source/aweXpect.Core/Expect.cs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

Test Results

    7 files   -     43      7 suites   - 43   44s ⏱️ - 7m 22s
1 253 tests  - 18 301  1 253 ✅  - 18 300  0 💤  - 1  0 ❌ ±0 
6 994 runs   - 94 111  6 994 ✅  - 94 110  0 💤  - 1  0 ❌ ±0 

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.
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenAwaited_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenAwaited_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_ShouldBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_WithoutReturnValue_ShouldBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenNotAwaited_WithoutReturnValue_WithVerifyInMethod_ShouldStillBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedStatically_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedStatically_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedWithStaticUsing_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerifiedWithStaticUsing_WithoutReturnValue_ShouldNotBeFlagged
aweXpect.Analyzers.Tests.AwaitExpectationAnalyzerTests ‑ WhenVerified_ShouldNotBeFlagged
…
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message4423b274-f425-4286-af9d-08794e109cd6")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message553c658a-660a-47b0-b30b-463fdedc8aac")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "message7e654865-43bc-4309-8779-e5897f131792")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "messagea0d9dde7-296d-4a0d-9b29-750d0f36b7b4")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "messagee632dc70-67b5-4346-8fb1-25a1ccdebf5f")
aweXpect.Core.Tests.Core.Exceptions.FailExceptionTests ‑ Message_ShouldBeSet(message: "messagef8dc1fb7-88b5-43ea-981e-1346252c2108")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message10875704-9725-4354-bc63-fd140eb1e66e")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message89d15c57-b47d-4867-b67e-9f49d3e8a4e8")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "message93f261a8-d22b-4fac-a276-24db7345e901")
aweXpect.Core.Tests.Core.Exceptions.SkipExceptionTests ‑ Message_ShouldBeSet(message: "messageae225d5e-05d7-4f2f-9be8-7ad94e2021fa")
…

♻️ This comment has been updated with latest results.

vbreuss added 2 commits May 10, 2026 12:52
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.
@vbreuss vbreuss changed the title feat: support Is<T>/IsExactly<T> in generic contexts via IThatSubject feat: support Is<T>/IsExactly<T> in generic contexts via IThatSubject May 10, 2026
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.
Copilot AI review requested due to automatic review settings May 10, 2026 11:01
@vbreuss vbreuss enabled auto-merge (squash) May 10, 2026 11:01
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
24.2% Coverage on New Code (required ≥ 50%)
33.6% Duplication on New Code (required ≤ 30%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread Source/aweXpect.Core/Core/Constraints/IsExactlyOfTypeConstraint.cs
Comment thread Pipeline/Build.cs
@vbreuss vbreuss merged commit 0264375 into main May 10, 2026
16 of 17 checks passed
@vbreuss vbreuss deleted the topic/enable-is-isexactly branch May 10, 2026 11:11
@github-actions
Copy link
Copy Markdown
Contributor

This is addressed in release v2.32.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change The changes require a new major version enhancement New feature or request state: released The issue is released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is for generic types not working

2 participants