Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types#5366
Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types#5366
Conversation
Relax generic constraint from `class` to `class?` so nullable reference types (e.g. `string?`) can be used without CS8620/CS8634 warnings. Fixes #5360
There was a problem hiding this comment.
Code Review
Summary
This is a clean, minimal, and correct fix. The change from where T : class to where T : class? is exactly the right approach to eliminate CS8620/CS8634 warnings when using nullable reference types with Arg.IsNull<T>() and Arg.IsNotNull<T>().
Analysis
The fix is correct. In C# nullable-aware contexts:
where T : class— constrains to non-nullable reference types only, causing the compiler to warn whenstring?is passed as the type argumentwhere T : class?— constrains to reference types (both nullable and non-nullable), which is what these methods actually need
The underlying matchers (NullMatcher<T> and NotNullMatcher<T>) already use T? in their Matches signatures, confirming they were designed with nullable intent. The constraint was simply lagging behind.
One Thing to Consider (not a blocker)
Currently neither IsNull nor IsNotNull supports Nullable<T> value types (e.g. int?). With where T : class?, Arg.IsNull<int?>() still won't compile because int? (Nullable<int>) is a struct. This is a pre-existing gap and outside the scope of this PR, but worth noting as a potential follow-up issue — users with methods like void Foo(int? x) can't use these matchers today.
If support for nullable value types is desirable in the future, a separate overload or unconstrained generic with runtime null check would be the path forward.
Verdict
The change is backward-compatible, fixes a real developer-experience issue with no side effects, and the PR description is clear and accurate. Approved.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 23 |
TIP This summary will be updated as you push new changes. Give us feedback
Remove the `where T : class?` constraint so these matchers work with nullable value types such as `int?`. Users can now write `Arg.IsNull<int?>()` for methods with nullable value-type parameters.
Updated Code Review (Follow-up)This is an update to my earlier review. The PR has evolved significantly from what I initially analyzed — the constraint change is more substantial than What ChangedThe final implementation completely removes the generic constraint from // Before
public static Arg<T> IsNull<T>() where T : class => new(new NullMatcher<T>());
public static Arg<T> IsNotNull<T>() where T : class => new(new NotNullMatcher<T>());
// After (this PR)
public static Arg<T> IsNull<T>() => new(new NullMatcher<T>());
public static Arg<T> IsNotNull<T>() => new(new NotNullMatcher<T>());This is a broader change than described in the PR summary (which only mentions relaxing to Correctness AnalysisFor For non-nullable value types (e.g. This isn't necessarily a blocking concern (users shouldn't be calling
Option 2 — More Precise API (optional follow-up)If you want to prevent the vacuous // Overload for reference types (including nullable reference types)
public static Arg<T> IsNull<T>() where T : class? => new(new NullMatcher<T>());
// Overload for nullable value types
public static Arg<T> IsNull<T>() where T : struct => new(new NullMatcher<T?>()); // returns Arg<T?> implicitlyHowever, this adds complexity and is arguably over-engineering for a mocking DSL. The current unconstrained approach is pragmatic and correct for all realistic use cases. TestsThe two new tests ( One minor note: VerdictThe change is correct and backward-compatible. The PR description understates what was done (the constraint is fully removed, not just relaxed to My previous comment flagging nullable value type support as a future gap has been addressed by this PR. Approved. |
…ue types Arg.IsNull<int>() and Arg.IsNotNull<int>() compile but are semantically vacuous — a non-nullable value type can never be null. TM005 warns at the call site and suggests using the nullable form (e.g. int?) instead.
There was a problem hiding this comment.
Code Review
This is a clean, well-thought-out fix. The approach of removing the constraint + adding a Roslyn analyzer to catch the now-possible-but-meaningless case is the right architectural pattern — better than keeping a compile-time constraint that over-restricts valid usage.
What's Good
- Constraint removal is correct: The underlying
NullMatcher<T>andNotNullMatcher<T>already useis null/is not nullpattern matching, which handlesNullable<T>correctly at runtime. No changes needed there. - Analyzer correctness: The
OriginalDefinition.SpecialType == SpecialType.System_Nullable_Tcheck correctly distinguishesNullable<T>(a value type that IS nullable) from plain structs. This is the right way to do this in Roslyn. - Namespace hierarchy match in
IsArgNullMethodis robust — it won't accidentally trigger on a user-definedArgclass that happens to have anIsNullmethod. - Test coverage is solid across the expected happy/sad paths.
Issues to Address
1. TM005 is out of numerical order in Rules.cs
TM005 is declared before TM004, which breaks the numeric ordering convention the file otherwise follows. This is a minor thing but makes it harder to navigate as more rules accumulate.
2. Missing analyzer test: nullable reference type (string?)
The analyzer tests cover string (non-nullable reference) but not string? (nullable reference type). Reference types with the nullable annotation (?) are still reference types — IsValueType is false for them — so the analyzer correctly skips them. But since string? was specifically called out in the PR description as a scenario that previously caused CS8620/CS8634 warnings, it's worth adding an explicit "does not report" test to lock in this behavior:
3. INullableValueConsumer is defined in BasicMockTests.cs but used in ArgumentMatcherTests.cs
The file-level comment in BasicMockTests.cs says "Test interfaces used across mock tests", which is a reasonable home for shared interfaces. But this creates an implicit coupling — someone reading ArgumentMatcherTests.cs needs to hunt for where INullableValueConsumer comes from. Consider either:
- Moving it to a dedicated
TestInterfaces.csfile (mirrors the existing comment intent), or - Keeping it in
BasicMockTests.csis acceptable since the file already serves as a shared interface container.
Not a blocker, just worth being intentional about.
Nice-to-Have (Not Blocking)
A code fix provider paired with TM005 that auto-suggests appending ? (e.g., changing Arg.IsNull<int>() → Arg.IsNull<int?>()) would significantly improve DX. Not required for this PR, but worth a follow-up issue.
Overall this is a solid fix. The two things worth addressing before merge are the rule ordering in Rules.cs (trivial) and the nullable reference type analyzer test gap (important for regression protection).
There was a problem hiding this comment.
Code Review
This is a clean, well-thought-out fix. The approach of removing the constraint + adding a Roslyn analyzer to catch the now-possible-but-meaningless case is the right architectural pattern — better than keeping a compile-time constraint that over-restricts valid usage.
What's Good
- Constraint removal is correct: The underlying
NullMatcher<T>andNotNullMatcher<T>already useis null/is not nullpattern matching, which handlesNullable<T>correctly at runtime. No changes needed there. - Analyzer correctness: The
OriginalDefinition.SpecialType == SpecialType.System_Nullable_Tcheck correctly distinguishesNullable<T>(a value type that IS nullable) from plain structs. This is the right Roslyn idiom. - Namespace hierarchy match in
IsArgNullMethodis robust — it won't accidentally trigger on a user-definedArgclass that happens to have anIsNullmethod. - Test coverage is solid across the expected happy/sad paths.
Issues to Address
1. TM005 is out of numerical order in Rules.cs
TM005 is declared before TM004, breaking the numeric ordering convention the file otherwise follows. As more rules accumulate this makes navigation harder.
Current order: TM001, TM002, TM003, TM005, TM004
Expected order: TM001, TM002, TM003, TM004, TM005
2. Missing analyzer test: nullable reference type (string?)
The analyzer tests cover string (non-nullable reference type) but not string? (nullable reference type). Reference types with the ? annotation still have IsValueType == false, so the analyzer correctly skips them — but since string? was specifically called out in the PR description as a previously-broken scenario (CS8620/CS8634), an explicit "does not report" test would lock in this behavior:
[Test]
public async Task IsNull_With_Nullable_Reference_Type_Does_Not_Report()
{
await Verifier.VerifyAnalyzerAsync(
ArgStub + """
#nullable enable
public class TestClass
{
public void Test()
{
TUnit.Mocks.Arguments.Arg.IsNull<string?>();
}
}
"""
);
}3. INullableValueConsumer is defined in BasicMockTests.cs but used in ArgumentMatcherTests.cs
The existing comment at the top of BasicMockTests.cs describes it as a home for "Test interfaces used across mock tests", which is reasonable. But this creates an implicit dependency — readers of ArgumentMatcherTests.cs need to hunt for where INullableValueConsumer is declared. A dedicated TestInterfaces.cs would make this explicit. Not a blocker — just worth being intentional about.
Nice-to-Have (Not Blocking)
A code fix provider paired with TM005 that auto-suggests appending ? (e.g., Arg.IsNull<int>() → Arg.IsNull<int?>()) would improve DX. Worth a follow-up issue.
Overall this is a solid fix. The two things most worth addressing before merge are the rule ordering in Rules.cs (trivial) and the missing nullable reference type analyzer test (important for regression protection given the PR's stated motivation).
Move TM005 after TM004 to restore numeric ordering in Rules.cs. Add explicit tests that Arg.IsNull/IsNotNull<string?>() does not trigger TM005, locking in the nullable reference type behaviour.
There was a problem hiding this comment.
Code Review
This is a well-executed fix. The approach is correct: remove the overly-restrictive where T : class constraint (the runtime matchers already worked via is null/is not null), then compensate with a Roslyn analyzer to catch the genuinely broken case.
What's Good
Clean runtime fix — NullMatcher<T> and NotNullMatcher<T> already used value is null/value is not null pattern matching which handles both reference and nullable value types correctly. The constraint was purely a compile-time restriction that didn't reflect reality.
Analyzer as compensation — Adding TM005 to warn on Arg.IsNull<int>() (non-nullable struct, can never be null) is exactly the right trade-off. Users get the runtime flexibility they need, but still get guidance when they've made a logical error.
Namespace chain check in IsArgNullMethod — The full namespace walk is the correct Roslyn approach for identifying a specific well-known method without taking a package reference.
Test coverage — Both the integration tests and analyzer tests cover the full matrix: non-nullable struct (warns), nullable value type (no warn), reference type (no warn), nullable reference type (no warn).
Minor Issues
INullableValueConsumer placed in the wrong file — The interface is defined in BasicMockTests.cs but is only used in ArgumentMatcherTests.cs. This creates a subtle coupling between unrelated test files. Consider moving it to ArgumentMatcherTests.cs (where it's consumed) or a shared TestInterfaces.cs file.
TM005 missing description parameter — The other rules (TM001–TM004) don't have it either, but it's worth noting that a description string gives IDE tooling richer hover text and is surfaced in dotnet_diagnostic suppression docs. Not a blocker, just a gap consistent with the rest of the file.
Behavior note (not a bug): When T = int (non-nullable struct, triggering TM005), NullMatcher<int>.Matches(int? value) will be called and will always return false. The diagnostic makes this a warning, not an error. If you want to prevent the "silent never-matches" case more strictly, TM005 could be DiagnosticSeverity.Error. But Warning is defensible if you want to avoid breaking existing code during upgrade.
Summary
Solid implementation. The single actionable item is relocating INullableValueConsumer to ArgumentMatcherTests.cs. Everything else is well-reasoned.
…tions - Move INullableValueConsumer to ArgumentMatcherTests.cs where it is used - Promote TM005 to DiagnosticSeverity.Error (non-nullable IsNull/IsNotNull is always wrong, not just suspicious) - Add description strings to all rules (TM001-TM005) for richer IDE hover text and dotnet_diagnostic suppression documentation
There was a problem hiding this comment.
Updated Code Review
This is a follow-up to my earlier reviews. The PR now fully addresses the concern I raised — let me document the final state.
Previous Concern: Addressed
My prior review flagged that removing where T : class would allow semantically vacuous usages like Arg.IsNull<int>() (which can never match since int is never null). The new ArgIsNullNonNullableAnalyzer (TM005) directly closes that gap with a compile-time error.
What's New In This Version
TM005 Analyzer (ArgIsNullNonNullableAnalyzer.cs) — correct and well-structured:
- Fires on
SyntaxKind.InvocationExpression— efficient, avoids broad symbol walks IsArgNullMethoduses full namespace qualification (TUnit.Mocks.Arguments.Arg) — avoids false positives from otherArgclasses- Correctly excludes
Nullable<T>viaOriginalDefinition.SpecialType == SpecialType.System_Nullable_T DiagnosticSeverity.Erroris appropriate — callingArg.IsNull<int>()is always wrong
Analyzer Tests (ArgIsNullNonNullableAnalyzerTests.cs) — comprehensive coverage:
- Reports for non-nullable struct (
int,bool) - Does not report for
int?,string,string? - The
ArgStubcorrectly reflects the unconstrained signature — good test hygiene
TM001–TM004 descriptions added — small but welcome improvement for IDE tooltips and docs.
One Minor Observation
In the ArgStub used by analyzer tests:
public struct Arg<T> { }The real Arg<T> is presumably a struct or class — this is fine for analyzer testing since the analyzer only inspects the method symbol, not the return type. Just worth noting that the stub doesn't need to exactly match the real type; it only needs to satisfy the namespace/method/generic-arity check in IsArgNullMethod.
Verdict
The implementation is correct, complete, and well-tested. The three-part solution (constraint removal + runtime matchers + TM005 analyzer) is the right architecture:
- Constraint removal — enables
int?usage that was previously blocked - Runtime matchers — already handled null checks correctly via
is null/is not null - TM005 analyzer — prevents the nonsensical
Arg.IsNull<int>()usage from silently compiling
All concerns from previous reviews have been addressed. Approved.
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.23.7 to 1.28.7. <details> <summary>Release notes</summary> _Sourced from [TUnit.Core's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.28.7 <!-- Release notes generated using configuration in .github/release.yml at v1.28.7 --> ## What's Changed ### Other Changes * fix: prevent StringBuilder race in console interceptor during parallel tests by @thomhurst in thomhurst/TUnit#5414 ### Dependencies * chore(deps): update tunit to 1.28.5 by @thomhurst in thomhurst/TUnit#5415 **Full Changelog**: thomhurst/TUnit@v1.28.5...v1.28.7 ## 1.28.5 <!-- Release notes generated using configuration in .github/release.yml at v1.28.5 --> ## What's Changed ### Other Changes * perf: eliminate redundant builds in CI pipeline by @thomhurst in thomhurst/TUnit#5405 * perf: eliminate store.ToArray() allocation on mock behavior execution hot path by @thomhurst in thomhurst/TUnit#5409 * fix: omit non-class/struct constraints on explicit interface mock implementations by @thomhurst in thomhurst/TUnit#5413 ### Dependencies * chore(deps): update tunit to 1.28.0 by @thomhurst in thomhurst/TUnit#5406 **Full Changelog**: thomhurst/TUnit@v1.28.0...v1.28.5 ## 1.28.0 <!-- Release notes generated using configuration in .github/release.yml at v1.28.0 --> ## What's Changed ### Other Changes * fix: resolve build warnings in solution by @thomhurst in thomhurst/TUnit#5386 * Perf: Optimize MockEngine hot paths (~30-42% faster) by @thomhurst in thomhurst/TUnit#5391 * Move Playwright install into pipeline module by @thomhurst in thomhurst/TUnit#5390 * perf: optimize solution build performance by @thomhurst in thomhurst/TUnit#5393 * perf: defer per-class JIT via lazy test registration + parallel resolution by @thomhurst in thomhurst/TUnit#5395 * Perf: Generate typed HandleCall<T1,...> overloads to eliminate argument boxing by @thomhurst in thomhurst/TUnit#5399 * perf: filter generated attributes to TUnit-related types only by @thomhurst in thomhurst/TUnit#5402 * fix: generate valid mock class names for generic interfaces with non-built-in type args by @thomhurst in thomhurst/TUnit#5404 ### Dependencies * chore(deps): update tunit to 1.27.0 by @thomhurst in thomhurst/TUnit#5392 * chore(deps): update dependency path-to-regexp to v8 by @thomhurst in thomhurst/TUnit#5378 **Full Changelog**: thomhurst/TUnit@v1.27.0...v1.28.0 ## 1.27.0 <!-- Release notes generated using configuration in .github/release.yml at v1.27.0 --> ## What's Changed ### Other Changes * Fix Dependabot security vulnerabilities in docs site by @thomhurst in thomhurst/TUnit#5372 * fix: use 0.0.0-scrubbed sentinel version in snapshot scrubber to avoid false Dependabot alerts by @thomhurst in thomhurst/TUnit#5374 * Speed up Engine.Tests by removing ProcessorCount parallelism cap by @thomhurst in thomhurst/TUnit#5379 * ci: add concurrency groups to cancel redundant workflow runs by @thomhurst in thomhurst/TUnit#5373 * Add scope-aware initialization and disposal OpenTelemetry spans to trace timeline and HTML report by @Copilot in thomhurst/TUnit#5339 * Add WithInnerExceptions() for fluent AggregateException assertion chaining by @thomhurst in thomhurst/TUnit#5380 * Drop net6.0 and net7.0 TFMs, keep net8.0+ and netstandard2.x by @thomhurst in thomhurst/TUnit#5387 * Remove all [Obsolete] members and migrate callers by @thomhurst in thomhurst/TUnit#5384 * Add AssertionResult.Failed overload that accepts an Exception by @thomhurst in thomhurst/TUnit#5388 ### Dependencies * chore(deps): update dependency mockolate to 2.3.0 by @thomhurst in thomhurst/TUnit#5370 * chore(deps): update tunit to 1.25.0 by @thomhurst in thomhurst/TUnit#5371 * chore(deps): update dependency minimatch to v9.0.9 by @thomhurst in thomhurst/TUnit#5375 * chore(deps): update dependency path-to-regexp to v0.2.5 by @thomhurst in thomhurst/TUnit#5376 * chore(deps): update dependency minimatch to v10 by @thomhurst in thomhurst/TUnit#5377 * chore(deps): update dependency picomatch to v4 by @thomhurst in thomhurst/TUnit#5382 * chore(deps): update dependency svgo to v4 by @thomhurst in thomhurst/TUnit#5383 * chore(deps): update dependency path-to-regexp to v1 [security] by @thomhurst in thomhurst/TUnit#5385 **Full Changelog**: thomhurst/TUnit@v1.25.0...v1.27.0 ## 1.25.0 <!-- Release notes generated using configuration in .github/release.yml at v1.25.0 --> ## What's Changed ### Other Changes * Fix missing `default` constraint on explicit interface implementations with unconstrained generics by @thomhurst in thomhurst/TUnit#5363 * feat(mocks): add ReturnsAsync typed factory overload with method parameters by @thomhurst in thomhurst/TUnit#5367 * Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types by @thomhurst in thomhurst/TUnit#5366 * refactor(mocks): use file-scoped types for generated implementation details by @thomhurst in thomhurst/TUnit#5369 * Compress HTML report JSON data and minify CSS by @thomhurst in thomhurst/TUnit#5368 ### Dependencies * chore(deps): update tunit to 1.24.31 by @thomhurst in thomhurst/TUnit#5356 * chore(deps): update dependency mockolate to 2.2.0 by @thomhurst in thomhurst/TUnit#5357 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5365 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5364 **Full Changelog**: thomhurst/TUnit@v1.24.31...v1.25.0 ## 1.24.31 <!-- Release notes generated using configuration in .github/release.yml at v1.24.31 --> ## What's Changed ### Other Changes * Fix Aspire 13.2.0+ timeout caused by ProjectRebuilderResource being awaited by @Copilot in thomhurst/TUnit#5335 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5349 * Fix nullable IParsable type recognition in source generator and analyzer by @Copilot in thomhurst/TUnit#5354 * fix: resolve race condition in HookExecutionOrderTests by @thomhurst in thomhurst/TUnit#5355 * Fix MaxExternalSpansPerTest cap bypass when Activity.Parent chain is broken by @Copilot in thomhurst/TUnit#5352 ### Dependencies * chore(deps): update tunit to 1.24.18 by @thomhurst in thomhurst/TUnit#5340 * chore(deps): update dependency stackexchange.redis to 2.12.14 by @thomhurst in thomhurst/TUnit#5343 * chore(deps): update verify to 31.15.0 by @thomhurst in thomhurst/TUnit#5346 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5348 **Full Changelog**: thomhurst/TUnit@v1.24.18...v1.24.31 ## 1.24.18 <!-- Release notes generated using configuration in .github/release.yml at v1.24.18 --> ## What's Changed ### Other Changes * feat(mocks): shorter, more readable generated mock type names by @thomhurst in thomhurst/TUnit#5334 * Fix DisposeAsync() ordering for nested property injection by @Copilot in thomhurst/TUnit#5337 ### Dependencies * chore(deps): update tunit to 1.24.13 by @thomhurst in thomhurst/TUnit#5331 **Full Changelog**: thomhurst/TUnit@v1.24.13...v1.24.18 ## 1.24.13 <!-- Release notes generated using configuration in .github/release.yml at v1.24.13 --> ## What's Changed ### Other Changes * perf(mocks): optimize MockEngine for lower allocation and faster verification by @thomhurst in thomhurst/TUnit#5319 * Remove defunct `UseTestingPlatformProtocol` reference for vscode by @erwinkramer in thomhurst/TUnit#5328 * perf(aspnetcore): prevent thread pool starvation during parallel WebApplicationTest server init by @thomhurst in thomhurst/TUnit#5329 * fix TUnit0073 for when type from from another assembly by @SimonCropp in thomhurst/TUnit#5322 * Fix implicit conversion operators bypassed in property injection casts by @Copilot in thomhurst/TUnit#5317 * fix(mocks): skip non-virtual 'new' methods when discovering mockable members by @thomhurst in thomhurst/TUnit#5330 * feat(mocks): IFoo.Mock() discovery with generic fallback and ORP resolution by @thomhurst in thomhurst/TUnit#5327 ### Dependencies * chore(deps): update tunit to 1.24.0 by @thomhurst in thomhurst/TUnit#5315 * chore(deps): update aspire to 13.2.1 by @thomhurst in thomhurst/TUnit#5323 * chore(deps): update verify to 31.14.0 by @thomhurst in thomhurst/TUnit#5325 ## New Contributors * @erwinkramer made their first contribution in thomhurst/TUnit#5328 **Full Changelog**: thomhurst/TUnit@v1.24.0...v1.24.13 ## 1.24.0 <!-- Release notes generated using configuration in .github/release.yml at v1.24.0 --> ## What's Changed ### Other Changes * perf: optimize TUnit.Mocks hot paths by @thomhurst in thomhurst/TUnit#5304 * fix: resolve System.Memory version conflict on .NET Framework (net462) by @thomhurst in thomhurst/TUnit#5303 * fix: resolve CS0460/CS0122/CS0115 when mocking concrete classes from external assemblies by @thomhurst in thomhurst/TUnit#5310 * feat(mocks): parameterless Returns() and ReturnsAsync() for async methods by @thomhurst in thomhurst/TUnit#5309 * Fix typo in NUnit manual migration guide by @aa-ko in thomhurst/TUnit#5312 * refactor(mocks): unify Mock.Of<T>() and Mock.OfPartial<T>() into single API by @thomhurst in thomhurst/TUnit#5311 * refactor(mocks): clean up Mock API surface by @thomhurst in thomhurst/TUnit#5314 * refactor(mocks): remove generic/untyped overloads from public API by @thomhurst in thomhurst/TUnit#5313 ### Dependencies * chore(deps): update tunit to 1.23.7 by @thomhurst in thomhurst/TUnit#5305 * chore(deps): update dependency mockolate to 2.1.1 by @thomhurst in thomhurst/TUnit#5307 ## New Contributors * @aa-ko made their first contribution in thomhurst/TUnit#5312 **Full Changelog**: thomhurst/TUnit@v1.23.7...v1.24.0 Commits viewable in [compare view](thomhurst/TUnit@v1.23.7...v1.28.7). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
where T : class?constraint fromArg.IsNull<T>()andArg.IsNotNull<T>()string?)int?) — previously a gap where users couldn't use these matchers for methods withint?,bool?, etc. parametersNullMatcher<T>,NotNullMatcher<T>) already handled nullable types correctly viais null/is not nullFixes #5360
Test plan
Arg.IsNull<string?>()compiles without warningsArg.IsNotNull<string?>()compiles without warningsArg.IsNull<string>()existing usage still worksArg.IsNull<int?>()now compiles and matches null correctlyArg.IsNotNull<int?>()now compiles and matches non-null correctly