Uh oh!
There was an error while loading. Please reload this page.
Fix nullable annotations on generic math interfaces - #74025
Conversation
ghost
commented
Aug 16, 2022
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
ghost
commented
Aug 16, 2022
Tagging subscribers to this area: @dotnet/area-system-numerics Issue Details
cc: @jeffhandley, @tannergooding, @drewnoakes, @jaredpar, @RikkiGibson, @MadsTorgersen
|
RikkiGibson
commented
Aug 16, 2022
I agree with all the changes except the "oblivious TOther" change—but that's only because I haven't paged in that particular problem yet. I did want to raise some additional potential usability issues that maybe we can reason through together: If someone decides to implement Conversely if someone was trying to ensure their caller provides an operator which can accept null, they might use a constraint like where It feels like the most "robust" pattern is for type authors to implement In general it seems like it would make sense to sprinkle |
stephentoub
commented
Aug 16, 2022
Interesting. Earlier today I actually asked @jaredpar whether he thought there were any uses for us properly annotating the interfaces for co/contravariance, as I couldn't think of any. I guess there is one. I'll push an additional commit with what that would look like and we can decide whether it makes sense. |
d976d00 to
197d96fComparestephentoub
commented
Aug 16, 2022
I pushed a commit with what I think the co/contravariance would look like. Two oddities are IAdditiveIdentity and IMultiplicativeIdentity, in which TSelf isn’t used in any of the members, so I left it as invariant even though it could be either covariant or contravariant. |
stephentoub
commented
Aug 16, 2022
cc: @terrajobst, @bartonjs |
terrajobst
commented
Aug 16, 2022
What's the null state for the out parameter in |
Depends on the T. If T is non-nullable, then the null state is not-null. If T is nullable, then the null state is maybe-null. We use |
dakersnar
left a comment
There was a problem hiding this comment.
First commit LGTM, besides a couple clarifying questions.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
For the The intent here is that I know there are also various edge cases with It might overall be safer if we say that |
stephentoub
commented
Aug 17, 2022
I'm fine removing the variance commit; I added it in response to Rikki's comments, and I didn't have any scenarios for it. His scenario doesn't actually involve anything other than TSelf from a runtime perspective, but the possibility of TSelf vs TSelf?, and using variance to allow for that distinction. But that scenario also requires it on TSelf, so if we're talking about reverting the variance changes, I'd be inclined to just revert them all-up, and revisit them in .NET 8 (though Jared raised the possibility that adding variance later could be a breaking change in its impact on overload resolution). |
tannergooding
commented
Aug 17, 2022
I don't have a strong preference here. I'm also unsure on how impactful such a break will be in practice considering the expected usage for most of these types between .NET 7 and .NET 8. For
I know that we have For the other operators they need at least one parameter to be the implementing type which should always be |
stephentoub
commented
Aug 17, 2022
Yup. But, we also have |
- All `where TSelf : ...` constraints become `where TSelf : ...?`. Without this, trying to define a type like `Matrix<T> where T : INumber<T>?` in order to support nullable T types warns because `INumber<T>` constrains its `T` (`TSelf`) to be non-nullable. - All `where TOther : ...` constraints are changed to be oblivious. They can't be correctly annotated as there's no way to express the nullability relationship with the nullability of TSelf. - Use `[MaybeNullWhen(false)] out T` instead of `[NotNullWhen(true)] out T?`, as we do with other generics, since if the instantiation of `T` is nullable, we can't guarantee `NotNullWhen(true)`. - Make `IEqualityOperators` `==` and `!=` accept `TSelf?`. This keeps it consistent with `IEquatable<T>.Equals(T?)`, `IEqualityComparer<in T>.Equals(T?, T?)`, `IEqualityComparer.Equals(object?, object?)`, `IStructuralEquatable.Equals(object?, IEqualityComparer)`, and `object.Equals(object?)` which all allow null even if generic and the generic is non-null. It in turn enables checks like `T.Zero == default` without nullability warnings.
197d96f to
56aa719Comparestephentoub
commented
Aug 17, 2022
Removed the extraneous @RikkiGibson, are you ok with this? Any other feedback? |
Top line: yeah, I think it is fine to keep these type parameters invariant for .NET 7. IEquatable is invariant somewhat because of compat and it is justified somewhat in terms of design (maybe a comparer written for Base is not going to do the right thing on Derived if Derived introduces additional state). To "make up" for this the compiler special cases IEquatable to make it "nullable-variant". SharpLab. I actually wonder to what extent being able to do this explicitly on interfaces like
This actually does seem suspect. Why would it make sense to convert from
Yeah, it means that some conversions that used to be invalid would become valid, and that would affect the viability/betterness of overloads, etc. My gut feeling is that is likely to be a low-impact break. Would it be helpful to "protect" this space by constraining some of the type parameters to |
stephentoub
commented
Aug 17, 2022
Blocking classes from participating in generic math is a gigantic hammer, one I'm certainly not going to wield while @tannergooding is on vacation. I'd like to get these NRT changes in for RC1, and I'll open a separate issue for the remaining things to be investigated when Tanner is back online. |
terrajobst
commented
Aug 17, 2022
Agreed. Let's not exclude classes. |
stephentoub
commented
Aug 18, 2022
/backport to release/7.0-rc1 |
Started backporting to release/7.0-rc1: https://github.com/dotnet/runtime/actions/runs/2879292036 |
where TSelf : ...constraints becomewhere TSelf : ...?. Without this, trying to define a type likeMatrix<T> where T : INumber<T>?in order to support nullable T types warns becauseINumber<T>constrains itsT(TSelf) to be non-nullable.where TOther : ...constraints are changed to be oblivious. They can't be correctly annotated as there's no way to express the nullability relationship with the nullability of TSelf.[MaybeNullWhen(false)] out Tinstead of[NotNullWhen(true)] out T?, as we do with other generics, since if the instantiation ofTis nullable, we can't guaranteeNotNullWhen(true).IEqualityOperators==and!=acceptTSelf?. This keeps it consistent withIEquatable<T>.Equals(T?),IEqualityComparer<in T>.Equals(T?, T?),IEqualityComparer.Equals(object?, object?),IStructuralEquatable.Equals(object?, IEqualityComparer), andobject.Equals(object?)which all allow null even if generic and the generic is non-null. It in turn enables checks likeT.Zero == defaultwithout nullability warnings.cc: @jeffhandley, @tannergooding, @dakersnar, @jaredpar, @RikkiGibson, @MadsTorgersen
Fixes#73855