Attempt to fix nullability for e.g. #973 - #976
Conversation
… types Change predicate signatures from Predicate<T?> to Predicate<T> in Arg.Is and ExpressionArgumentMatcher, aligning with the compatibility API which already used non-nullable predicates. Replace the unboxing cast in IsSatisfiedBy with an 'argument is T typed' type check in both ExpressionArgumentMatcher and the GenericToNonGenericMatcherProxy. A null argument passed for a non-nullable type now returns false instead of throwing or coercing to default.
it already throws when it's not assignable
Before it had to be banged
|
@dtchepak no problem, happy to help. I believe the current changes should be sufficient to fix the issues. However, I only detected and fixed things which showed up in our own usage of nsubstitute so I might have missed things. I think reverting the changes would be a bit of waste, given that the direction of enabling nullable is a good idea. Anyhow, these changes are ready for review. |
|
Testing this out now. Is the using NUnit.Framework;
namespace NSubstitute.Acceptance.Specs.FieldReports;
public class Issue973_MatchingWithNullability
{
#nullable enable
public interface ISomething
{
int DoSomething(string s);
int DoSomethingNullable(string? s);
}
[Test]
public void Match_non_null()
{
var sub = Substitute.For<ISomething>();
sub.DoSomething(Arg.Is<string>(x => x.StartsWith("12"))).Returns(42);
Assert.That(sub.DoSomething("123"), Is.EqualTo(42));
Assert.That(sub.DoSomething("abc"), Is.EqualTo(0));
}
[Test]
public void Match_nullable()
{
var sub = Substitute.For<ISomething>();
sub.DoSomethingNullable(Arg.Is<string>(x => x.StartsWith("12"))).Returns(42);
sub.DoSomethingNullable(Arg.Is<string?>(x => x == null)).Returns(456);
Assert.That(sub.DoSomethingNullable("123"), Is.EqualTo(42));
Assert.That(sub.DoSomethingNullable("hi"), Is.EqualTo(0));
Assert.That(sub.DoSomethingNullable(null), Is.EqualTo(456));
/*
failed Match_nullable
Assert.That(sub.DoSomethingNullable(null), Is.EqualTo(456))
Expected: 456
But was: 0
*/
#nullable restore
} |
|
what is purpose of GenericTypeArgumentsAreCompatible. Is it related to #974 ? Maybe better to revert old PR until author will propose fixed version? |
|
Other changes looks good |
For me looks correct |
How is it correct that it doesn't return 456? How else would I setup an |
Null argument also matched on the non nullable variant
|
@dtchepak |
No it was not related to #974 GenericTypeArgumentsAreCompatible was required to not use a derived setup when generic functions were called with a base type. I've added a test for this, see |
|
Hi @zvirja, was hoping to get your thoughts here based on your initial concern about enabling nullability. I'm a bit torn between reverting the initial nullability change vs. proceeding with @jdb0123's fix. @jdb0123's fix addresses the issues found i their code base, but we're not sure what other cases will emerge. Any thoughts on whether to press on with this, or revert and maybe take another look at this later? |
|
I propose following:
and release as 6.0.1 and then lets process next feedback if any |
|
@dtchepak My personal suggestion would be to revert nullability for the public API introduced in #856. This is a testing library and nullability is always weird in the unit test projects, as we don't track it strictly there anyway. Another argument is that by the library's nature we don't have any info about nullability of the code we are mocking. Like when we have This PR is a clear demonstration of that deeper issue, as it starts marking types non-nullable which in fact could be null. Look at So I vote for reverting that PR. We gave it a shot, we saw the issues and the proposed fix shows that there is no a clean way of fixing it. As for #977 - it's a different beast. |
I'm not sure why you would choose the revert the changes. As far as we are aware there are not further issues once we merge #976 and #977. So I would just go for which @Romfos already suggested:
If you merge the 2 PR's, release, close the existing nullable related issues. Then we will hear back from other use cases which were missed, if any, and we can fix those. |
Current PR offers hacky solution, as now it states things are non-nullable while they could be nullable. See the Also NSubstitute is not really compatible with nullability. Take for example this syntax: sut.Method(default, default).ReturnsForAnyArgs(42);That's how you usually use Again, one of many examples. The issue is about nullability being incorrectly assigned - the problem is that nullability is just unsolvable with the API we provide and instead of returning wrong nullability (like we do in this PR) we should rather disable it at all. |
|
@zvirja can you give me a full code example of the issues you mention. They are not clear to me from your text. |
|
May I suggest unlisting version 6.0.0 from NuGet if this discussion goes on? More and more users are updating to 6.0.0 every day and waste time trying to fix the new nullable warnings. |
This reverts commit 6c37da9 from PR #856, reversing changes made to ee11ef0. #976 has demonstrated that we need more fundamental lib changes to effectively support nullability, so we'll revert to the pre v6.0 behaviour and revisit later. # Conflicts: # BreakingChanges.md # CHANGELOG.md # src/NSubstitute/Compatibility/Arg.Compat.cs # src/NSubstitute/Core/CallInfo.cs
|
@jdb0123 I understand wanting to keep nullability support, but based on the 6.0.0 attempt I think we might be better off starting with the internals. Once the internals are nullability-aware i think we'll be in a better position to enable it for callers where it makes sense (keeping in mind examples like @zvirja's where we may explicitly want to ignore nullables for some extension methods.) |
Definitely something i considered @cremor . I'm about to publish v6.1.0 with nullability reverted, so I'm tempted to keep 6.0 published for people running it without nullability as they still get new matchers etc. Sound reasonable? |
|
@dtchepak Yes, sure. My intention was just for the latest version to not be broken. |
No description provided.