-
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
Null coalescing assignment for nullable value type should set state of nullable type not underlying type #67468
Changes from 6 commits
9db9155
e6766e4
f227740
29cf664
aa642aa
2d689b6
5750193
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 |
---|---|---|
|
@@ -2641,6 +2641,13 @@ protected override void JoinTryBlockState(ref LocalState self, ref LocalState ot | |
private void InheritDefaultState(TypeSymbol targetType, int targetSlot) | ||
{ | ||
Debug.Assert(targetSlot > 0); | ||
#if DEBUG | ||
var actualType = _variables[targetSlot].Symbol.GetTypeOrReturnType().Type; | ||
Debug.Assert(actualType is { }); | ||
Debug.Assert(actualType.ContainsErrorType() || | ||
targetType.ContainsErrorType() || | ||
isOfTypeOrBaseOrInterface(actualType, targetType)); | ||
#endif | ||
|
||
// Reset the state of any members of the target. | ||
var members = ArrayBuilder<(VariableIdentifier, int)>.GetInstance(); | ||
|
@@ -2652,6 +2659,43 @@ private void InheritDefaultState(TypeSymbol targetType, int targetSlot) | |
InheritDefaultState(symbol.GetTypeOrReturnType().Type, slot); | ||
} | ||
members.Free(); | ||
|
||
#if DEBUG | ||
static bool isOfTypeOrBaseOrInterface(TypeSymbol actualType, TypeSymbol expectedType) | ||
{ | ||
if (isType(actualType, expectedType)) | ||
{ | ||
return true; | ||
} | ||
if (actualType is NamedTypeSymbol namedType) | ||
{ | ||
if (namedType.IsInterface) | ||
{ | ||
return namedType.AllInterfacesNoUseSiteDiagnostics.Contains(expectedType); | ||
} | ||
else | ||
{ | ||
while (true) | ||
{ | ||
var baseType = namedType.BaseTypeNoUseSiteDiagnostics; | ||
if (baseType is null) | ||
{ | ||
return false; | ||
} | ||
if (isType(baseType, expectedType)) | ||
{ | ||
return true; | ||
} | ||
namedType = baseType; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
static bool isType(TypeSymbol a, TypeSymbol b) | ||
=> a.Equals(b, TypeCompareKind.AllIgnoreOptions); | ||
#endif | ||
} | ||
|
||
private NullableFlowState GetDefaultState(Symbol symbol) | ||
|
@@ -5105,12 +5149,26 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly | |
var leftState = this.State.Clone(); | ||
LearnFromNonNullTest(leftOperand, ref leftState); | ||
LearnFromNullTest(leftOperand, ref this.State); | ||
|
||
// If we are assigning the underlying value type to a nullable value type variable, | ||
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. I found this comment a little unclear. I think it should say that we do a special step to set the nullable variable's top level state within this block, then change the slot to later simulate an assignment from the RHS to the LHS's Value property, to update nullable states within the underlying value type. #Resolved |
||
// set the state of the .Value property of the variable. | ||
if (node.IsNullableValueTypeAssignment) | ||
{ | ||
Debug.Assert(targetType.Type.ContainsErrorType() || | ||
node.Type?.ContainsErrorType() == true || | ||
TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), node.Type, TypeCompareKind.AllIgnoreOptions)); | ||
if (leftSlot > 0) | ||
{ | ||
SetState(ref this.State, leftSlot, NullableFlowState.NotNull); | ||
leftSlot = GetNullableOfTValueSlot(targetType.Type, leftSlot, out _); | ||
} | ||
targetType = TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated); | ||
} | ||
|
||
TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand), trackMembers: false, AssignmentKind.Assignment); | ||
Debug.Assert(TypeSymbol.Equals(targetType.Type, rightResult.Type, TypeCompareKind.AllIgnoreOptions)); | ||
TrackNullableStateForAssignment(rightOperand, targetType, leftSlot, rightResult, MakeSlot(rightOperand)); | ||
|
||
Join(ref this.State, ref leftState); | ||
TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); | ||
SetResultType(node, 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.
Should we use some "classify conversion" API instead? e.g. if it's identity or implicit reference conversion, maybe that's good enough? #Resolved