-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce NotNull on method exit #42827
Conversation
a69138b
to
68f2edd
Compare
@dotnet/roslyn-compiler for review. Thanks |
var slot = GetOrCreateSlot(parameter); | ||
if (slot > 0) | ||
{ | ||
var parameterState = state[slot]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider inlining this variable to avoid calculating value in most cases.
if (value is null) | ||
{ | ||
throw new InvalidOperationException("This program location is unreachable."); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the throw
needed since the assert above will have failed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert above is not necessarily annotated, in some of our projects. The stricter new enforcement implemented in this PR caused bootstrap build to fail some projects...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you implement in terms RoslynDebug.Assert
instead of Debug.Assert
to solve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, indeed :-) I'll try using RoslynDebug.Assert
@@ -5506,6 +5506,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
<data name="WRN_ParameterConditionallyDisallowsNull_Title" xml:space="preserve"> | |||
<value>Parameter may not have a null value when exiting in some condition.</value> | |||
</data> | |||
<data name="WRN_ParameterDisallowsNull" xml:space="preserve"> | |||
<value>Parameter '{0}' may not have a null value when exiting.</value> | |||
</data> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Parameter may not have a null value when exiting" could imply the parameter must have a null value but might not. How about "Parameter must have a non-null value when exiting"? Similar comment for WRN_ParameterConditionallyDisallowsNull
.
interesting how the approach we came up with specifically to solve NotNullWhenAttribute ended up being useful for NotNullAttribute as well. 😄 |
|
||
class C<TStruct, TClass, TNotNull> | ||
where TStruct : struct | ||
where TClass : class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TClass
is not used.
@RikkiGibson @dotnet/roslyn-compiler for second review. Thanks |
} | ||
x = null; | ||
(x, _) = (null, 1); | ||
} // 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm. this makes it start to feel like a tradeoff for more complex methods. Imagine a complex method with many return points and many writes to some [NotNull] out
param. Would you feel lost if the nullability warnings were on the returns instead of the original assignments?
Are we also trying to improve the experience in scenarios where users write a value that might be null to the out param, and if it is null, they write some fallback value that's known to be non-null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there is a bit of trade-off. Warning sooner points to the location of the problematic assignment, but it also means that we flag temporary assignments (annoying).
For nullability feature in general, we lean away from anything that could be annoying and could hold you back from annotating your correct code as it already exists.
// (x, _) = (null, 1); // 5 | ||
Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(41, 19), | ||
// (42,5): warning CS8777: Parameter 'x' must have a non-null value when exiting. | ||
// } // 6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why enforce this on parameters with RefKind.None? Isn't the method just asserting about the value originally passed as an argument in that case?
On the other hand, it doesn't feel like there's a use case for assigning maybe-null to a NotNull, non-ref parameter, so it probably isn't very important to remove this warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it for a moment, it seems like unnecessary effort to attempt to remove this warning--but it might not be what we want to prevent this warning by just assigning not-null to the parameter. e.g.
void MyAssert1([NotNull] object? obj)
{
// hooray, we know the argument passed by the caller is non-null after the method returns
if (obj is null)
throw new ArgumentNullException(nameof(obj));
}
void MyAssert2([NotNull] object? obj)
{
obj = new object(); // we fixed the warning, hooray!
// but caller still might be holding a null..
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I don't have a strategy to distinguish between those two cases :-(
=> Debug.Assert(b, message); | ||
|
||
[Conditional("DEBUG")] | ||
public static void AssertNotNull<T>([NotNull]T value) where T : class? | ||
=> Debug.Assert(value is object, "Unexpected null reference"); | ||
public static void AssertNotNull<T>([NotNull] T value) where T : class? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really something to fix in this PR, but I don't understand why this method is generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it is so that it cannot be called with a struct parameter.
If it just took an object
parameter, then structs would box, rather than produce an error.
Fixes #42386
Closes #42981
Relates to PR #40789 (enforce unconditional attributes in method bodies)