-
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
Add invariant around nullable conditional state #72072
Conversation
@@ -1607,7 +1607,8 @@ public override BoundNode VisitUtf8String(BoundUtf8String node) | |||
|
|||
protected void SplitIfBooleanConstant(BoundExpression node) | |||
{ | |||
if (node.ConstantValueOpt is { IsBoolean: true, BooleanValue: bool booleanValue }) | |||
if (node.ConstantValueOpt is { IsBoolean: true, BooleanValue: bool booleanValue } | |||
&& node.Type.SpecialType == SpecialType.System_Boolean) |
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.
📝 EnumTests.EnumUnsupportedUnderlyingType
shows how we can get here.
private bool TypeAllowsConditionalState(TypeSymbol? type) | ||
{ | ||
return type is not null | ||
&& (type.SpecialType == SpecialType.System_Boolean || type.IsDynamic() || type.IsErrorType()); |
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.
📝 We could disallow conditional state for dynamic
but that degrades a couple of tests. #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 error type scenario is hit with LiftedRelationalOperation_UserDefined
. Presumably, we could move the fix upstream (unsplit some time during binary operator analysis). Let me know if you have a preference
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 think it's better to permit error type here than to try and adjust the bound tree shape.
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 but let's merge after #72062 to reduce potential for conflicts/churn.
return FlowAnalysisAnnotations.None; | ||
|
||
// Conditional annotations are ignored on parameters of non-boolean members. | ||
if (parameter.ContainingSymbol.GetTypeOrReturnType().Type.SpecialType != SpecialType.System_Boolean) |
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.
It seems like we are just using information on the parameter symbol to make these adjustments, did you consider moving this logic into property FlowAnalysisAnnotations instead? Or possibly we wouldn't want to do these adjustment in all places this property is used? #Resolved
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, I considered it (for this PR and previously when the annotations were added). We had landed on keeping the symbol API straightfoward, it shows what annotations are there. That said, I don't feel too strongly either way.
@cston what do you think?
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.
Having the adjustment here seems fine to me, unless all callers should see the adjusted values.
@@ -8004,10 +8054,9 @@ bool tryAsMemberOfSingleType(NamedTypeSymbol singleType, [NotNullWhen(true)] out | |||
|
|||
(BoundExpression operand, Conversion conversion) = RemoveConversion(node, includeExplicitConversions: true); | |||
SnapshotWalkerThroughConversionGroup(node, operand); | |||
if (targetType.SpecialType == SpecialType.System_Boolean && | |||
(operand.Type?.SpecialType == SpecialType.System_Boolean || operand.Type?.IsErrorType() == true)) | |||
if (TypeAllowsConditionalState(targetType.Type) && TypeAllowsConditionalState(operand.Type)) |
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.
FYI @RikkiGibson I integrated with the change from other PR #Resolved
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 seems more permissive than the version of the check from the other PR, is there any need to test when source or target type of the conversion is dynamic, for example, and we have a split state after visiting the operand?
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.
Added a test where a difference is observable (PatternOnTuple_IsExpression_DynamicCast
). There would be a nullable warning before this change.
@@ -3547,6 +3553,20 @@ private BoundNode VisitLValue(BoundNode node) | |||
return Visit(node, expressionIsRead: false); | |||
} | |||
|
|||
private bool TypeAllowsConditionalState(TypeSymbol? type) |
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.
static
#Resolved
@@ -4499,6 +4519,7 @@ private TypeSymbol VisitArrayInitialization(TypeSymbol type, BoundArrayInitializ | |||
var expression = GetConversionIfApplicable(expressions[i], expressionNoConversion); | |||
expressionTypes[i] = VisitConversion(expression, expressionNoConversion, conversions[i], inferredType, expressionTypes[i], checkConversion: true, | |||
fromExplicitCast: false, useLegacyWarnings: false, AssignmentKind.Assignment, reportRemainingWarnings: true, reportTopLevelWarnings: false); | |||
Unsplit(); |
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.
NotNullWhen_UserDefinedConversion_Completeness
hits the new assertion without this because VisitConversion
above leaves us in a split state in that scenario, which results in a split state at the end of the array initialization.
@@ -5825,6 +5846,8 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver) | |||
|
|||
resultType ??= node.Type?.SetUnknownNullabilityForReferenceTypes(); | |||
|
|||
UnsplitIfNeeded(resultType); |
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.
Likewise, ConditionalOperator_17
and ConditionalOperator_WithUserDefinedConversion_BoolOperand
hit the new assertion without this.
return FlowAnalysisAnnotations.None; | ||
|
||
// Conditional annotations are ignored on parameters of non-boolean members. | ||
if (parameter.ContainingSymbol.GetTypeOrReturnType().Type.SpecialType != SpecialType.System_Boolean) |
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.
Scenarios like NotNullWhenTrue_ReturningObject
and the other related tests I added NotNullWhen***_Returning***
. They would also hit the newly added assertion, leaving a split state after analyzing an invocation returning object
or some other types.
// Conditional annotations are ignored on parameters of non-boolean members. | ||
if (parameter.ContainingSymbol.GetTypeOrReturnType().Type.SpecialType != SpecialType.System_Boolean) | ||
{ | ||
var annotations = parameter.FlowAnalysisAnnotations; |
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.
As part of reviewing PR #72062, I was surprised to notice that we don't currently encode the rule that conditional states are only allowed for expressions of type
bool
(we only haveStateWhenTrue
andStateWhenFalse
).Here's a proposed assertion, along with a couple of fixes to unsplit when we're not dealing with a
bool
expression.