MaybeNullWhen and NotNullWhen attribute affects callers#36172
MaybeNullWhen and NotNullWhen attribute affects callers#36172jcouv merged 4 commits intodotnet:masterfrom
Conversation
07af513 to
9bef0fb
Compare
4115162 to
2f86a27
Compare
|
@dotnet/roslyn-compiler for review. |
| { | ||
| builder.Add(VisitArgumentEvaluate(arguments[i], GetRefKind(refKindsOpt, i), preserveConditionalState: false)); | ||
| (_, _, FlowAnalysisAnnotations parameterAnnotations) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded); | ||
|
|
There was a problem hiding this comment.
Perhaps simpler
FlowAnalysisAnnotations parameterAnnotations = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded).Annotations;#Resolved
| // check whether parameter would unsafely let a null out in the worse case | ||
| if (!argument.IsSuppressed) | ||
| { | ||
| ReportNullableAssignmentIfNecessary(parameterValue, lValueType, applyPostConditionsUnconditionally(parameterWithState, parameterAnnotations), useLegacyWarnings: false); |
There was a problem hiding this comment.
false [](start = 189, length = 5)
Should this be UseLegacyWarnings(argument, result.LValueType)? In case the parameter is a local, I would expect it to be a legacy warning. (I'm not sure why we call it a legacy warning. I think the meaning is that legacy is a non-safety warning) #Resolved
|
|
||
| void trackNullableStateForAssignment(BoundExpression parameterValue, TypeWithAnnotations lValueType, int targetSlot, TypeWithState parameterWithState, bool isSuppressed, FlowAnalysisAnnotations parameterAnnotations) | ||
| { | ||
| if (!IsConditionalState && !hasConditionalPostCondition(parameterAnnotations)) |
There was a problem hiding this comment.
IsConditionalState [](start = 21, length = 18)
Is there a risk of duplicate diagnostics on following parameters? #Resolved
There was a problem hiding this comment.
I don't think there is. Tracking assignments affects state, but does not produce diagnostics. #Resolved
|
|
||
| return typeWithState; | ||
| } | ||
|
|
There was a problem hiding this comment.
These could be collapsed into a single function if you pass the whenTrue and whenFalse enum bits in as a parameter. #WontFix
There was a problem hiding this comment.
I'm tempted to leave this as-is. I agree the code could be factored more, but I think it would become less readable. #Resolved
|
Thanks Neal! |
| { | ||
| _ = F1(t1, out var s2) | ||
| ? s2.ToString() // 1 | ||
| : s2.ToString(); // 2 |
There was a problem hiding this comment.
s2.ToString(); // 2 [](start = 14, length = 19)
I don't understand why there would be a warning here? #Resolved
There was a problem hiding this comment.
Imagine that M1 is invoked with T = string?. Then we're looking at F1<string?>(t1, out string? t2) which yields a maybe-null t2 in both branches.
In reply to: 291751778 [](ancestors = 291751778)
agocke
left a comment
There was a problem hiding this comment.
LGTM, with the assumption that design changes will come in later
| UseRvalueOnly(argument); // force use of flow result | ||
| } | ||
| else | ||
| switch (annotations & (FlowAnalysisAnnotations.AssertsTrue | FlowAnalysisAnnotations.AssertsFalse)) |
There was a problem hiding this comment.
Could annotations have both AssertsTrue and AssertsFalse? #Resolved
There was a problem hiding this comment.
Thanks. It looks like I unintentionally changed the behavior for that scenario. Added a test and fixed it. #Resolved
| if ((annotations & FlowAnalysisAnnotations.MaybeNull) != 0) | ||
| { | ||
| // MaybeNull and MaybeNullWhen | ||
| return TypeWithState.Create(typeWithState.Type, NullableFlowState.MaybeNull); |
There was a problem hiding this comment.
As mentioned offline, this should produce a nullable type, if one exists, right?
There was a problem hiding this comment.
I added a note to the work items list (#35816) and started thread with LDM.
| if ((annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull) | ||
| { | ||
| // NotNull | ||
| return TypeWithState.Create(typeWithState.Type, NullableFlowState.NotNull); |
Design notes:
In an invocation, we first visit the arguments and save the results. We unsplit after each argument, except for those marked with
AssertsTrue/AssertsFalse(which will be renamed toDoesNotReturnIf(bool)later on) where we drop half of the state.We then do type inference.
We then validate inbound values and conversions, considering pre-condition attributes. This all happens in an unsplit state.
We then validate outbound values and apply post-condition attributes:
refandoutarguments, we track an assignment from parameter to argumentin/by-value, we don't do any assignment, but we can still learn from various attributesRelates to #35816
LDM notes: https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-05-15.md
Fixes #35949 (complexity issue with lambdas in invocations with nullability annotation attributes) because
VisitArgumentsEvaluateHonoringAnnotationsused to visit arguments more, but that was removed. We now visit lambdas as often as in initial binding.