未验证 提交 84cf3ef5 编写于 作者: J Julien Couvreur 提交者: GitHub

Track l-value types in conditional operator (#34323)

Track l-values in conditional operator
Assignment to ref variable produces safety warnings
上级 7f809efa
......@@ -1367,8 +1367,8 @@ private static bool HasIdentityConversionInternal(TypeSymbol type1, TypeSymbol t
Debug.Assert((object)type1 != null);
Debug.Assert((object)type2 != null);
// Note, when we are paying attention to nullability, we ignore insignificant differences and oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny and TypeCompareKind.IgnoreInsignificantNullableModifiersDifference
// Note, when we are paying attention to nullability, we ignore oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny
var compareKind = includeNullability ?
TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes :
TypeCompareKind.AllIgnoreOptions;
......
......@@ -1365,7 +1365,6 @@ public override BoundNode VisitLocalDeclaration(BoundLocalDeclaration node)
protected override BoundExpression VisitExpressionWithoutStackGuard(BoundExpression node)
{
Debug.Assert(!IsConditionalState);
bool wasReachable = this.State.Reachable;
ResultType = _invalidType;
_ = base.VisitExpressionWithoutStackGuard(node);
TypeWithState resultType = ResultType;
......@@ -1375,7 +1374,7 @@ protected override BoundExpression VisitExpressionWithoutStackGuard(BoundExpress
Debug.Assert((object)resultType.Type != _invalidType.Type);
Debug.Assert(AreCloseEnough(resultType.Type, node.Type));
#endif
if (node.IsSuppressed || node.HasAnyErrors || !wasReachable)
if (node.IsSuppressed || node.HasAnyErrors || !IsReachable())
{
resultType = resultType.WithNotNullState();
SetResult(resultType, LvalueResultType);
......@@ -2332,38 +2331,73 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
public override BoundNode VisitConditionalOperator(BoundConditionalOperator node)
{
var isByRef = node.IsRef;
VisitCondition(node.Condition);
var consequenceState = this.StateWhenTrue;
var alternativeState = this.StateWhenFalse;
TypeWithState consequenceRValue;
TypeWithState alternativeRValue;
if (node.IsRef)
{
TypeWithAnnotations consequenceLValue;
TypeWithAnnotations alternativeLValue;
(consequenceLValue, consequenceRValue) = visitConditionalRefOperand(consequenceState, node.Consequence);
consequenceState = this.State;
(alternativeLValue, alternativeRValue) = visitConditionalRefOperand(alternativeState, node.Alternative);
Join(ref this.State, ref consequenceState);
TypeSymbol refResultType = node.Type.SetUnknownNullabilityForReferenceTypes();
if (IsNullabilityMismatch(consequenceLValue, alternativeLValue))
{
// l-value types must match
ReportNullabilityMismatchInAssignment(node.Syntax, consequenceLValue, alternativeLValue);
}
else if (!node.HasErrors)
{
refResultType = consequenceRValue.Type.MergeNullability(alternativeRValue.Type, VarianceKind.None);
}
var lValueAnnotation = consequenceLValue.NullableAnnotation.EnsureCompatible(alternativeLValue.NullableAnnotation);
var rValueState = consequenceRValue.State.Join(alternativeRValue.State);
SetResult(new TypeWithState(refResultType, rValueState), TypeWithAnnotations.Create(refResultType, lValueAnnotation));
return null;
}
BoundExpression consequence;
BoundExpression alternative;
Conversion consequenceConversion;
Conversion alternativeConversion;
TypeWithAnnotations consequenceResult;
TypeWithAnnotations alternativeResult;
bool consequenceEndReachable;
bool alternativeEndReachable;
bool isConstantTrue = IsConstantTrue(node.Condition);
bool isConstantFalse = IsConstantFalse(node.Condition);
if (isConstantTrue)
// In cases where one branch is unreachable, we don't need to Unsplit the state
if (!alternativeState.Reachable)
{
(alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
(consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
(alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, node.Alternative);
(consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, node.Consequence);
alternativeEndReachable = false;
consequenceEndReachable = IsReachable();
}
else if (isConstantFalse)
else if (!consequenceState.Reachable)
{
(consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
(alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
(consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, node.Consequence);
(alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, node.Alternative);
consequenceEndReachable = false;
alternativeEndReachable = IsReachable();
}
else
{
(consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
(consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, node.Consequence);
Unsplit();
consequenceState = this.State;
(alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
consequenceEndReachable = consequenceState.Reachable;
(alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, node.Alternative);
Unsplit();
alternativeEndReachable = this.State.Reachable;
Join(ref this.State, ref consequenceState);
}
......@@ -2375,145 +2409,100 @@ public override BoundNode VisitConditionalOperator(BoundConditionalOperator node
else
{
// Determine nested nullability using BestTypeInferrer.
// For constant conditions, we could use the nested nullability of the particular
// If a branch is unreachable, we could use the nested nullability of the other
// branch, but that requires using the nullability of the branch as it applies to the
// target type. For instance, the result of the conditional in the following should
// be `IEnumerable<object>` not `object[]`:
// object[] a = ...;
// IEnumerable<object?> b = ...;
// var c = true ? a : b;
BoundExpression consequencePlaceholder = CreatePlaceholderIfNecessary(consequence, consequenceResult);
BoundExpression alternativePlaceholder = CreatePlaceholderIfNecessary(alternative, alternativeResult);
BoundExpression consequencePlaceholder = CreatePlaceholderIfNecessary(consequence, consequenceRValue.ToTypeWithAnnotations());
BoundExpression alternativePlaceholder = CreatePlaceholderIfNecessary(alternative, alternativeRValue.ToTypeWithAnnotations());
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
// https://github.com/dotnet/roslyn/issues/30432: InferBestTypeForConditionalOperator should use node.IsRef.
resultType = BestTypeInferrer.InferBestTypeForConditionalOperator(consequencePlaceholder, alternativePlaceholder, _conversions, out _, ref useSiteDiagnostics);
}
TypeWithAnnotations visitResult;
if ((object)resultType != null)
NullableFlowState resultState;
if (resultType is null)
{
resultType = node.Type.SetUnknownNullabilityForReferenceTypes();
resultState = NullableFlowState.NotNull;
}
else
{
var resultTypeWithAnnotations = TypeWithAnnotations.Create(resultType);
TypeWithAnnotations convertedConsequenceResult = default;
TypeWithAnnotations convertedAlternativeResult = default;
TypeWithState convertedConsequenceResult = default;
TypeWithState convertedAlternativeResult = default;
if (!isConstantFalse)
if (consequenceEndReachable)
{
convertedConsequenceResult = convertResult(
node.Consequence,
consequence,
consequenceConversion,
resultTypeWithAnnotations,
consequenceResult);
consequenceRValue);
}
if (!isConstantTrue)
if (alternativeEndReachable)
{
convertedAlternativeResult = convertResult(
node.Alternative,
alternative,
alternativeConversion,
resultTypeWithAnnotations,
alternativeResult);
}
if (!convertedAlternativeResult.HasType)
{
Debug.Assert(convertedConsequenceResult.HasType);
visitResult = convertedConsequenceResult;
}
else if (!convertedConsequenceResult.HasType)
{
Debug.Assert(convertedAlternativeResult.HasType);
visitResult = convertedAlternativeResult;
}
else
{
visitResult = TypeWithAnnotations.Create(resultType,
convertedConsequenceResult.NullableAnnotation.Join(convertedAlternativeResult.NullableAnnotation));
alternativeRValue);
}
}
else
{
NullableAnnotation resultNullableAnnotation;
if (isConstantTrue)
{
resultNullableAnnotation = getNullableAnnotation(consequence, consequenceResult);
}
else if (isConstantFalse)
{
resultNullableAnnotation = getNullableAnnotation(alternative, alternativeResult);
}
else
{
resultNullableAnnotation = getNullableAnnotation(consequence, consequenceResult).Join(getNullableAnnotation(alternative, alternativeResult));
}
visitResult = TypeWithAnnotations.Create(node.Type.SetUnknownNullabilityForReferenceTypes(), resultNullableAnnotation);
resultState = convertedConsequenceResult.State.Join(convertedAlternativeResult.State);
}
LvalueResultType = visitResult;
ResultType = new TypeWithState(resultType, resultState);
return null;
NullableAnnotation getNullableAnnotation(BoundExpression expr, TypeWithAnnotations type)
{
if (type.HasType)
{
return type.GetValueNullableAnnotation();
}
if (expr.IsLiteralNullOrDefault())
{
return NullableAnnotation.Annotated;
}
return NullableAnnotation.Oblivious;
}
(BoundExpression, Conversion, TypeWithAnnotations) visitConditionalOperand(LocalState state, BoundExpression operand)
(BoundExpression, Conversion, TypeWithState) visitConditionalOperand(LocalState state, BoundExpression operand)
{
Conversion conversion;
SetState(state);
TypeWithAnnotations resultWithAnnotation;
if (isByRef)
{
resultWithAnnotation = VisitLvalueWithAnnotations(operand);
conversion = Conversion.Identity;
}
else
{
(operand, conversion) = RemoveConversion(operand, includeExplicitConversions: false);
Visit(operand);
if (node.HasErrors)
{
ResultType = ResultType.WithNotNullState();
}
Debug.Assert(!node.IsRef);
resultWithAnnotation = ResultType.ToTypeWithAnnotations();
}
(operand, conversion) = RemoveConversion(operand, includeExplicitConversions: false);
Visit(operand);
return (operand, conversion, ResultType);
}
return (operand, conversion, resultWithAnnotation);
(TypeWithAnnotations LValueType, TypeWithState RValueType) visitConditionalRefOperand(LocalState state, BoundExpression operand)
{
SetState(state);
Debug.Assert(node.IsRef);
TypeWithAnnotations lValueType = VisitLvalueWithAnnotations(operand);
return (lValueType, ResultType);
}
TypeWithAnnotations convertResult(
TypeWithState convertResult(
BoundExpression node,
BoundExpression operand,
Conversion conversion,
TypeWithAnnotations targetType,
TypeWithAnnotations operandType)
TypeWithState operandType)
{
return ApplyConversion(
node,
operand,
conversion,
targetType,
operandType.ToTypeWithState(),
operandType,
checkConversion: true,
fromExplicitCast: false,
useLegacyWarnings: false,
AssignmentKind.Assignment,
reportTopLevelWarnings: false).ToTypeWithAnnotations();
reportTopLevelWarnings: false);
}
}
bool IsReachable()
=> this.IsConditionalState ? (this.StateWhenTrue.Reachable || this.StateWhenFalse.Reachable) : this.State.Reachable;
/// <summary>
/// Placeholders are bound expressions with type and state.
/// But for typeless expressions (such as `null` or `(null, null)` we hold onto the original bound expression,
......@@ -4484,7 +4473,7 @@ private static bool UseLegacyWarnings(BoundExpression expr)
switch (expr.Kind)
{
case BoundKind.Local:
return !RequiresSafetyWarningWhenNullIntroduced(expr.Type);
return expr.GetRefKind() == RefKind.None && !RequiresSafetyWarningWhenNullIntroduced(expr.Type);
case BoundKind.Parameter:
RefKind kind = ((BoundParameter)expr).ParameterSymbol.RefKind;
return kind == RefKind.None && !RequiresSafetyWarningWhenNullIntroduced(expr.Type);
......@@ -5706,16 +5695,16 @@ private bool ReportPossibleNullReceiverIfNeeded(TypeSymbol type, NullableFlowSta
private static bool IsNullabilityMismatch(TypeWithAnnotations type1, TypeWithAnnotations type2)
{
// Note, when we are paying attention to nullability, we ignore insignificant differences and oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny and TypeCompareKind.IgnoreInsignificantNullableModifiersDifference
// Note, when we are paying attention to nullability, we ignore oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny
return type1.Equals(type2, TypeCompareKind.AllIgnoreOptions) &&
!type1.Equals(type2, TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes);
}
private static bool IsNullabilityMismatch(TypeSymbol type1, TypeSymbol type2)
{
// Note, when we are paying attention to nullability, we ignore insignificant differences and oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny and TypeCompareKind.IgnoreInsignificantNullableModifiersDifference
// Note, when we are paying attention to nullability, we ignore oblivious mismatch.
// See TypeCompareKind.UnknownNullableModifierMatchesAny
return type1.Equals(type2, TypeCompareKind.AllIgnoreOptions) &&
!type1.Equals(type2, TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册