-
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
Update argument state when parameter has not-null type #46072
Conversation
Could you add a couple more scenarios? I think those will work:
I'm not completely sure about this one:
Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:34557 in a298c6f. [](commit_id = a298c6f, deletion_comment = False) |
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.
Done with review pass (iteration 1). Looks nice, thanks!
MaybeNullWhen attribute cannot be applied multiple times, and seems a bit esoteric to test with a specially-crafted attribute, but will add the other test cases. |
I have added the requested tests. Please have another look at your convenience @jcouv. /cc @dotnet/roslyn-compiler |
=> !argument.IsSuppressed | ||
&& ((!parameterType.Type.IsTypeParameterDisallowingAnnotation() | ||
&& parameterType.NullableAnnotation.IsNotAnnotated()) | ||
|| parameterOriginalDefinitionType.Type is TypeParameterSymbol { HasNotNullConstraint: true }); |
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.
parameterOriginalDefinitionType [](start = 27, length = 31)
I didn't understand why we need to use the original definition here.
If we're dealing with an unsubstituted method, then the parameter is TNotNull
which we can check here (1).
If we're dealing with a substituted method, then the parameter was substituted with an unannotated type (2) or a safety diagnostic was reported on the substitution (3). Is scenario (3) what prompted you to use the original definition here? #Closed
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.
In scenario (3) adding this check allows us to update argument state to not-null which reduces cascading diagnostics in a similar fashion to updating argument state when the parameter type disallows null.
class C
{
void M<T>(T t) where T : notnull { }
void M1(string? s)
{
M(s); // warning: 'string?' does not meet the 'notnull' constraint
s.ToString(); // in master we get another warning here, but this is avoidable.
}
}
It occurs to me that if we do this here, we should probably do something similar for the 'class' constraint. #Closed
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'd be okay if this we didn't handle this cascading diagnostic at all, or if we split that to a different PR. We would report a warning on the constraint, then one warning on passing a bad value to M(s)
but no warning on s.ToString()
. #Closed
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.
@RikkiGibson From discussion with @cston, it feels like the second part of the PR should be reverted and not even addressed in a follow-up PR, as it is addressing a different scenario than the original point of this PR and what was discussed in LDM.
The main rationale from LDM is that we should assume that methods with non-nullable parameters really cannot handle null (one doesn't need to add an attribute) to express that. There was no discussion of avoiding cascades from violated constraints. #Closed
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 agree, it seems like this part of the change needs to be considered separately. I think I just misinterpreted the suggestion to add a T : notnull
test. #Closed
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 main rationale from LDM is that we should assume that methods with non-nullable parameters really cannot handle null
It does feel like this is also the case for type parameters constrained to notnull
or class
. However, whether to change anything in nullable analysis for this scenario is up to the LDM. #Closed
Have resolved merge conflicts, but have not fixed a semantic break related to |
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.
LGTM Thanks (iteration 6)
@dotnet/roslyn-compiler Please review |
} | ||
} | ||
"; | ||
var comp = CreateNullableCompilation(source); | ||
comp.VerifyDiagnostics( | ||
// (9,12): warning CS8604: Possible null reference argument for parameter 't' in 'void Program<T>.M0(T t)'. | ||
// M0(t); // 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.
Thanks for catching/fixing that scenario #Closed
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.
LGTM Thanks (iteration 7)
void M1() | ||
{ | ||
T t = default; | ||
M0(t); // 1 |
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.
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 expect the state to get upgraded from "maybe-default" to "maybe-null"
I didn't follow.
I could see that either (1) the state is unchanged (which I believe is the case with this PR), or (2) the state would be set to not-null (ie. assuming that the invocation throws).
In reply to: 460329898 [](ancestors = 460329898)
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. I think I inferred a principle that doesn't quite apply to unconstrained generics. My thinking is that if you call a method with a value it's not meant to accept, we update the argument state in a way that avoids warnings on subsequent calls. Thus if you call some method with an unconstrained generic parameter (requiring a maybe-null or not-null state), and you pass an argument with a maybe-default state, we change the argument state to maybe-null.
This is based on a conception of the flow states where:
- "maybe-default" means a variable of a generic type may contain null, even if the type argument disallows it.
- "maybe-null" means a variable of a generic type may contain null if the type argument allows it.
- "not-null" means the variable doesn't contain null.
I think this is not something to change for this PR, but I will modify the test to record this behavior.
Please test Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:34474 in c2c725f. [](commit_id = c2c725f, deletion_comment = False) |
Please add a similar test for an unconstrained type parameter. static void M<T>([DisallowNull] T t) { }
static void M1<T>(T t)
{
M(t);
t.ToString();
} Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:34680 in c2c725f. [](commit_id = c2c725f, deletion_comment = False) |
Sorry, I meant for the constraint on void M0<T>(T t) { }
void M1<T>(T? t) where T : class
{
// ...
} And: void M0<T>(T t) { }
void M1<T>(T t) where T : class?
{
// ...
} The Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:34495 in 790899e. [](commit_id = 790899e, deletion_comment = False) |
…-pointers * upstream/master: (207 commits) Update argument state when parameter has not-null type (dotnet#46072) Fix TypeWithAnnotations.ToTypeWithState() for (untyped) null literal (dotnet#46344) Update README (dotnet#46136) Revert "Revert "Support nullable annotations on unconstrained type parameters"" Revert "Support nullable annotations on unconstrained type parameters (dotnet#45993)" Fix type in publish data Update VSIXExpInstaller version to one available on ADO Update publish data for 16.8 Update version of RichCodeNav.EnvVarDump A fixed initializer must be bound to its natural type (dotnet#46293) Update features merged into 16.7p4 (dotnet#46229) Async-streams: disposal should continue without jump within a finally (dotnet#46188) Recommend default in type constraint, but not record (dotnet#46311) Add use site diagnostics to IsUnmanaged (dotnet#46114) Add another flaky test. Ensure NuGet connections use TLS 1.2 Update to Microsoft.CodeAnalysis.Testing 1.0.1-beta1.20374.2 Skip flaky test. Fix build break. (dotnet#46303) Skip a flaky test Relates to dotnet#46304 ...
} | ||
|
||
[Fact] | ||
public void UnannotatedTypeArgument_NullableClassConstrained_DoesNotUpdateArgumentState() |
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 making this test a [Theory]
and using an argument to enumerate all the different constraint combinations where this is now valid.
); | ||
} | ||
|
||
[Fact] |
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 adding a test here there is an override
that removes the nullability annotation and calls through both the base and derived type and shows the outcome on the null state of the argument. Basically making sure we are considering the base and derived method annotation as appropriate.
I actually can't remember off hand if we look at the declaration method or the override when considering annotations hence I can't remember what the correct behavior is here. Hence a test 😄
…to function-pointer-type-lookup * upstream/features/function-pointers: (212 commits) Correct public API number. Update argument state when parameter has not-null type (dotnet#46072) Fix TypeWithAnnotations.ToTypeWithState() for (untyped) null literal (dotnet#46344) Update README (dotnet#46136) Revert "Revert "Support nullable annotations on unconstrained type parameters"" Revert "Support nullable annotations on unconstrained type parameters (dotnet#45993)" Fix type in publish data Update VSIXExpInstaller version to one available on ADO Update publish data for 16.8 Update version of RichCodeNav.EnvVarDump A fixed initializer must be bound to its natural type (dotnet#46293) Update features merged into 16.7p4 (dotnet#46229) Async-streams: disposal should continue without jump within a finally (dotnet#46188) Recommend default in type constraint, but not record (dotnet#46311) Add use site diagnostics to IsUnmanaged (dotnet#46114) Add another flaky test. Ensure NuGet connections use TLS 1.2 Update to Microsoft.CodeAnalysis.Testing 1.0.1-beta1.20374.2 Skip flaky test. Fix build break. (dotnet#46303) ...
🦔
Fixes #43383