-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Only enforce Member/NotNullWhen on constants #48425
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
Changes from all commits
a38e715
a92fefb
93ff57a
7c59ff8
2acf4dd
c2602c1
9ff27bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -611,35 +611,72 @@ void enforceMemberNotNullWhenForPendingReturn(PendingBranch pendingReturn, Bound | |
| { | ||
| if (pendingReturn.IsConditionalState) | ||
| { | ||
| enforceMemberNotNullWhen(returnStatement.Syntax, sense: true, pendingReturn.StateWhenTrue); | ||
| enforceMemberNotNullWhen(returnStatement.Syntax, sense: false, pendingReturn.StateWhenFalse); | ||
| if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } }) | ||
| { | ||
| enforceMemberNotNullWhen(returnStatement.Syntax, sense: value, pendingReturn.State); | ||
| return; | ||
| } | ||
|
|
||
| if (!pendingReturn.StateWhenTrue.Reachable || !pendingReturn.StateWhenFalse.Reachable) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (_symbol is MethodSymbol method) | ||
| { | ||
| foreach (var memberName in method.NotNullWhenTrueMembers) | ||
| { | ||
| enforceMemberNotNullWhenIfAffected(returnStatement.Syntax, sense: true, method.ContainingType.GetMembers(memberName), pendingReturn.StateWhenTrue, pendingReturn.StateWhenFalse); | ||
| } | ||
|
|
||
| foreach (var memberName in method.NotNullWhenFalseMembers) | ||
| { | ||
| enforceMemberNotNullWhenIfAffected(returnStatement.Syntax, sense: false, method.ContainingType.GetMembers(memberName), pendingReturn.StateWhenFalse, pendingReturn.StateWhenTrue); | ||
| } | ||
| } | ||
| } | ||
| else if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } }) | ||
| { | ||
| enforceMemberNotNullWhen(returnStatement.Syntax, sense: value, pendingReturn.State); | ||
| } | ||
| } | ||
|
|
||
| void enforceMemberNotNullWhen(SyntaxNode? syntaxOpt, bool sense, LocalState state) | ||
| void enforceMemberNotNullWhenIfAffected(SyntaxNode? syntaxOpt, bool sense, ImmutableArray<Symbol> members, LocalState state, LocalState otherState) | ||
| { | ||
| if (!state.Reachable) | ||
| foreach (var member in members) | ||
| { | ||
| return; | ||
| // For non-constant values, only complain if we were able to analyze a difference for this member between two branches | ||
| if (memberHasBadState(member, state) != memberHasBadState(member, otherState)) | ||
| { | ||
| reportMemberIfBadConditionalState(syntaxOpt, sense, member, state); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void enforceMemberNotNullWhen(SyntaxNode? syntaxOpt, bool sense, LocalState state) | ||
| { | ||
| if (_symbol is MethodSymbol method) | ||
| { | ||
| var notNullMembers = sense ? method.NotNullWhenTrueMembers : method.NotNullWhenFalseMembers; | ||
| foreach (var memberName in notNullMembers) | ||
| { | ||
| foreach (var member in method.ContainingType.GetMembers(memberName)) | ||
| { | ||
| if (memberHasBadState(member, state)) | ||
| { | ||
| // Member '{name}' must have a non-null value when exiting with '{sense}'. | ||
| Diagnostics.Add(ErrorCode.WRN_MemberNotNullWhen, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation(), member.Name, sense ? "true" : "false"); | ||
| } | ||
| reportMemberIfBadConditionalState(syntaxOpt, sense, member, state); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void reportMemberIfBadConditionalState(SyntaxNode? syntaxOpt, bool sense, Symbol member, LocalState state) | ||
| { | ||
| if (memberHasBadState(member, state)) | ||
| { | ||
| // Member '{name}' must have a non-null value when exiting with '{sense}'. | ||
| Diagnostics.Add(ErrorCode.WRN_MemberNotNullWhen, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation(), member.Name, sense ? "true" : "false"); | ||
| } | ||
| } | ||
|
|
||
| bool memberHasBadState(Symbol member, LocalState state) | ||
| { | ||
| switch (member.Kind) | ||
|
|
@@ -767,12 +804,45 @@ void enforceNotNullWhenForPendingReturn(PendingBranch pendingReturn, BoundReturn | |
| { | ||
| if (pendingReturn.IsConditionalState) | ||
| { | ||
| enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: true, stateWhen: pendingReturn.StateWhenTrue); | ||
| enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: false, stateWhen: pendingReturn.StateWhenFalse); | ||
| if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } }) | ||
| { | ||
| enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: value, stateWhen: pendingReturn.State); | ||
| return; | ||
| } | ||
|
|
||
| if (!pendingReturn.StateWhenTrue.Reachable || !pendingReturn.StateWhenFalse.Reachable) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| foreach (var parameter in parameters) | ||
| { | ||
| // For non-constant values, only complain if we were able to analyze a difference for this parameter between two branches | ||
| if (GetOrCreateSlot(parameter) is > 0 and var slot && pendingReturn.StateWhenTrue[slot] != pendingReturn.StateWhenFalse[slot]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (doesn't need to be addressed in this PR) I have been thinking that it doesn't make sense for a parameter to ever not have a slot--it will never be constant, for example. Perhaps we should use a helper for getting parameter slots that throws if we fail to get a slot for it. #WontFix |
||
| { | ||
| reportParameterIfBadConditionalState(returnStatement.Syntax, parameter, sense: true, stateWhen: pendingReturn.StateWhenTrue); | ||
| reportParameterIfBadConditionalState(returnStatement.Syntax, parameter, sense: false, stateWhen: pendingReturn.StateWhenFalse); | ||
| } | ||
| } | ||
| } | ||
| else if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } }) | ||
| { | ||
| // example: return (bool)true; | ||
| enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: value, stateWhen: pendingReturn.State); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void reportParameterIfBadConditionalState(SyntaxNode syntax, ParameterSymbol parameter, bool sense, LocalState stateWhen) | ||
| { | ||
| if (parameterHasBadConditionalState(parameter, sense, stateWhen)) | ||
| { | ||
| // Parameter '{name}' must have a non-null value when exiting with '{sense}'. | ||
| Diagnostics.Add(ErrorCode.WRN_ParameterConditionallyDisallowsNull, syntax.Location, parameter.Name, sense ? "true" : "false"); | ||
| } | ||
| } | ||
|
|
||
| void enforceNotNull(SyntaxNode? syntaxOpt, LocalState state) | ||
| { | ||
| if (!state.Reachable) | ||
|
|
@@ -812,11 +882,7 @@ void enforceParameterNotNullWhen(SyntaxNode syntax, ImmutableArray<ParameterSymb | |
|
|
||
| foreach (var parameter in parameters) | ||
| { | ||
| if (parameterHasBadConditionalState(parameter, sense, stateWhen)) | ||
| { | ||
| // Parameter '{name}' must have a non-null value when exiting with '{sense}'. | ||
| Diagnostics.Add(ErrorCode.WRN_ParameterConditionallyDisallowsNull, syntax.Location, parameter.Name, sense ? "true" : "false"); | ||
| } | ||
| reportParameterIfBadConditionalState(syntax, parameter, sense, stateWhen); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -2428,6 +2494,7 @@ private bool TryGetReturnType(out TypeWithAnnotations type, out FlowAnalysisAnno | |
| } | ||
|
|
||
| SetResult(node, GetAdjustedResult(type.ToTypeWithState(), slot), type); | ||
| SplitIfBooleanConstant(node); | ||
| return null; | ||
| } | ||
|
|
||
|
|
@@ -8144,9 +8211,27 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter | |
| return null; | ||
| } | ||
|
|
||
| private void SplitIfBooleanConstant(BoundExpression node) | ||
| { | ||
| if (node.ConstantValue is { IsBoolean: true, BooleanValue: bool booleanValue }) | ||
| { | ||
| Split(); | ||
| if (booleanValue) | ||
| { | ||
| StateWhenFalse = UnreachableState(); | ||
| } | ||
| else | ||
| { | ||
| StateWhenTrue = UnreachableState(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public override BoundNode? VisitFieldAccess(BoundFieldAccess node) | ||
| { | ||
| var updatedSymbol = VisitMemberAccess(node, node.ReceiverOpt, node.FieldSymbol); | ||
|
|
||
| SplitIfBooleanConstant(node); | ||
| SetUpdatedSymbol(node, node.FieldSymbol, updatedSymbol); | ||
| return null; | ||
| } | ||
|
|
@@ -8606,8 +8691,9 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node) | |
| switch (node.OperatorKind) | ||
| { | ||
| case UnaryOperatorKind.BoolLogicalNegation: | ||
| VisitCondition(node.Operand); | ||
| SetConditionalState(StateWhenFalse, StateWhenTrue); | ||
| Visit(node.Operand); | ||
| if (IsConditionalState) | ||
| SetConditionalState(StateWhenFalse, StateWhenTrue); | ||
| resultType = adjustForLifting(ResultType); | ||
| break; | ||
| case UnaryOperatorKind.DynamicTrue: | ||
|
|
@@ -8960,19 +9046,7 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress | |
| Debug.Assert(!IsConditionalState); | ||
| SetResultType(node, TypeWithState.Create(node.Type, node.Type?.CanContainNull() != false && node.ConstantValue?.IsNull == true ? NullableFlowState.MaybeDefault : NullableFlowState.NotNull)); | ||
|
|
||
| if (node.ConstantValue?.IsBoolean == true) | ||
| { | ||
| Split(); | ||
| if (node.ConstantValue.BooleanValue) | ||
| { | ||
| StateWhenFalse = UnreachableState(); | ||
| } | ||
| else | ||
| { | ||
| StateWhenTrue = UnreachableState(); | ||
| } | ||
| } | ||
|
|
||
| SplitIfBooleanConstant(node); | ||
| return result; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
Shouldn't we only suppress when both states are unreachable? We could have just done a test and thrown in the other branch, but accidentally messed up the order of the branches and returned incorrectly. #ByDesign
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.
Same for the other tests of this condition.
In reply to: 503602424 [](ancestors = 503602424)
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 only way to have one state being unreachable is using a constant condition (which is case above).
In reply to: 503602558 [](ancestors = 503602558,503602424)