-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Adjust nullability analysis for extension operators. #79103
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
base: features/extensions
Are you sure you want to change the base?
Conversation
Related to dotnet#29605. Related to dotnet#29961. Related to dotnet#79011.
@@ -5313,6 +5312,58 @@ | |||
var inferredResult = InferResultNullability(operatorKind, method, returnType, leftType, rightType); | |||
|
|||
return inferredResult; | |||
} | |||
|
|||
MethodSymbol ReInferBinaryOperator( |
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.
private #Pending
extension, | ||
method.OriginalDefinition.ParameterTypesWithAnnotations, | ||
method.OriginalDefinition.ParameterRefKinds, | ||
[new BoundExpressionWithNullability(leftOperand.Syntax, leftOperand, leftUnderlyingType.ToTypeWithAnnotations(compilation).NullableAnnotation, leftUnderlyingType.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.
In analysis of invocations (VisitCall
/VisitArguments
), we use GetArgumentsForMethodTypeInference
to get inputs to MethodTypeInferrer.Infer
. Do we need the same thing here (it has extra cases to deal with lambda, collection expressions and typeless expressions)? #Pending
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.
GetArgumentsForMethodTypeInference
operates in terms of VisitResult
for arguments, this function gets TypeWithState
instead. Moreover, the passed in TypeWithState
is getting adjusted by the caller for lifted scenarios. Given that, it doesn't look trivial to switch from TypeWithState
to VisitResult
as an input. I think the current implementation is going to work for majority of scenarios, I will leave a prototype comment to follow up.
break; | ||
} | ||
|
||
AdjustConditionalState(binary); |
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.
Should AdjustConditionalState
be applied to the left-most operand too (ie. in the first switch
in this method)? #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.
Should
AdjustConditionalState
be applied to the left-most operand too (ie. in the firstswitch
in this method)?
For case BoundBinaryOperator
, VisitCondition
does that and, as can be observed in AbstractFlowPass
, AdjustConditionalState
isn't called explicitly for the left-most operand.
For case BoundUserDefinedConditionalLogicalOperator
, AfterLeftChildOfBinaryConditionalLogicalOperatorHasBeenVisited
does
Unsplit();
Split();
right at the beginning, which is equivalent to AdjustConditionalState
for non-Boolean case.
} | ||
} | ||
|
||
protected override void AfterLeftChildOfBinaryLogicalOperatorHasBeenVisited(BoundExpression node, BoundExpression right, bool isAnd, bool isBool, ref LocalState leftTrue, ref LocalState leftFalse) | ||
private static void GetBinaryConditionalOperatorInfo(BinaryOperatorKind kind, out bool isAnd, out bool isBool) |
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.
Visit(node.Right); | ||
TypeWithState rightType = ResultType; | ||
SetResultType(node, InferResultNullability(node.OperatorKind, node.Method, node.Type, leftType, rightType)); | ||
AfterRightChildOfBinaryLogicalOperatorHasBeenVisited(node.Right, isAnd, isBool, ref leftTrue, ref leftFalse); |
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.
nit: it looks like the AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
signature could be simplified by removing the ref
on last two parameters (no callers care for any updates this method might make) #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.
it looks like the
AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
signature could be simplified by removing the ref on last two parameters (no callers care for any updates this method might make)
I am not going to mess with the signature of existing method from the base class. Passing state by ref actually makes sense when the state is a struct.
SetResultType(node, InferResultNullabilityOfBinaryLogicalOperator(node, leftType, rightType)); | ||
GetBinaryConditionalOperatorInfo(binary.OperatorKind, out bool isAnd, out bool isBool); | ||
Debug.Assert(!isBool); | ||
SetState(isAnd ? leftTrue : leftFalse); |
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 strange. leftTrue
and leftFalse
hold the same state here. Consider removing this line and the Split
a couple of lines above.
Removing the ref
from last two parameters of AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
as suggested earlier will also simplify this further (no need for separate leftTrue
/leftFalse
) #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.
The split is actually required to properly calculate the state after the operator. See the logic around joining states in AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
. It is true that at the beginning the states are the same, but the one that we set here is going to diverge as we keep visiting things.
Apart from ref
the input for AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
indeed could be somewhat simplified if we move calculations around isAnd
, leftTrue, and
leftFalse``` into the callers. But I am not interested doing this due to the following reasons:
- this moves complexity (calculation that could be implemented with a mistake) to every consumer.
- The
AfterRightChildOfBinaryLogicalOperatorHasBeenVisited
is existing method, which otherwise is not getting touched.
It seems that for static operators we currently don't care about the nullability annotations in the extension parameter. Should we check that as a declaration warning?
|
Do we have a test for the following:
Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs:16074 in b279a80. [](commit_id = b279a80, 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 (commit 1)
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
I think the behavior is intentional and desirable. In reply to: 2998670074 Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs:14916 in b279a80. [](commit_id = b279a80, deletion_comment = False) |
Because the scenarios are related to the issue and they were not working properly before my changes. I also modified the code in the function from the issue. In reply to: 2998710883 Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs:8068 in b279a80. [](commit_id = b279a80, deletion_comment = False) |
Related to #29605.
Related to #29961.
Related to #79011.
Relates to test plan #76130