未验证 提交 4a94aece 编写于 作者: C Charles Stoner 提交者: GitHub

Replace slot watermark (#33455)

上级 c4bb224f
...@@ -750,7 +750,7 @@ private void ReportNullabilityMismatchInAssignment(SyntaxNode syntaxNode, object ...@@ -750,7 +750,7 @@ private void ReportNullabilityMismatchInAssignment(SyntaxNode syntaxNode, object
/// <summary> /// <summary>
/// Update tracked value on assignment. /// Update tracked value on assignment.
/// </summary> /// </summary>
private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWithAnnotations targetType, int targetSlot, TypeSymbolWithAnnotations valueType, int valueSlot = -1, int slotWatermark = -1) private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWithAnnotations targetType, int targetSlot, TypeSymbolWithAnnotations valueType, int valueSlot = -1)
{ {
Debug.Assert(value != null); Debug.Assert(value != null);
Debug.Assert(!IsConditionalState); Debug.Assert(!IsConditionalState);
...@@ -780,11 +780,6 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi ...@@ -780,11 +780,6 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi
InheritDefaultState(targetSlot); InheritDefaultState(targetSlot);
if (slotWatermark < 0)
{
slotWatermark = GetSlotWatermark();
}
// https://github.com/dotnet/roslyn/issues/33428: Can the areEquivalentTypes check be removed // https://github.com/dotnet/roslyn/issues/33428: Can the areEquivalentTypes check be removed
// if InheritNullableStateOfMember asserts the member is valid for target and value? // if InheritNullableStateOfMember asserts the member is valid for target and value?
if (areEquivalentTypes(targetType, valueType)) // https://github.com/dotnet/roslyn/issues/29968 Allow assignment to base type. if (areEquivalentTypes(targetType, valueType)) // https://github.com/dotnet/roslyn/issues/29968 Allow assignment to base type.
...@@ -796,12 +791,12 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi ...@@ -796,12 +791,12 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi
// When that issue is fixed, Nullable<T> should be handled there instead. // When that issue is fixed, Nullable<T> should be handled there instead.
if (valueSlot > 0) if (valueSlot > 0)
{ {
InheritNullableStateOfTrackableType(targetSlot, valueSlot, slotWatermark); InheritNullableStateOfTrackableType(targetSlot, valueSlot, skipSlot: targetSlot);
} }
} }
else if (EmptyStructTypeCache.IsTrackableStructType(targetType.TypeSymbol)) else if (EmptyStructTypeCache.IsTrackableStructType(targetType.TypeSymbol))
{ {
InheritNullableStateOfTrackableStruct(targetType.TypeSymbol, targetSlot, valueSlot, isDefaultValue: IsDefaultValue(value), slotWatermark); InheritNullableStateOfTrackableStruct(targetType.TypeSymbol, targetSlot, valueSlot, isDefaultValue: IsDefaultValue(value), skipSlot: targetSlot);
} }
} }
} }
...@@ -810,8 +805,6 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi ...@@ -810,8 +805,6 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi
t1.TypeSymbol.Equals(t2.TypeSymbol, TypeCompareKind.AllIgnoreOptions); t1.TypeSymbol.Equals(t2.TypeSymbol, TypeCompareKind.AllIgnoreOptions);
} }
private int GetSlotWatermark() => this.nextVariableSlot;
private void ReportNonSafetyDiagnostic(SyntaxNode syntax) private void ReportNonSafetyDiagnostic(SyntaxNode syntax)
{ {
ReportNonSafetyDiagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, syntax); ReportNonSafetyDiagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, syntax);
...@@ -846,24 +839,29 @@ private void ReportDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params ...@@ -846,24 +839,29 @@ private void ReportDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params
} }
} }
private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isDefaultValue, int slotWatermark) private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isDefaultValue, int skipSlot = -1)
{ {
Debug.Assert(targetSlot > 0); Debug.Assert(targetSlot > 0);
Debug.Assert(EmptyStructTypeCache.IsTrackableStructType(targetType)); Debug.Assert(EmptyStructTypeCache.IsTrackableStructType(targetType));
if (skipSlot < 0)
{
skipSlot = targetSlot;
}
// https://github.com/dotnet/roslyn/issues/29619 Handle properties not backed by fields. // https://github.com/dotnet/roslyn/issues/29619 Handle properties not backed by fields.
// See ModifyMembers_StructPropertyNoBackingField and PropertyCycle_Struct tests. // See ModifyMembers_StructPropertyNoBackingField and PropertyCycle_Struct tests.
foreach (var field in _emptyStructTypeCache.GetStructInstanceFields(targetType)) foreach (var field in _emptyStructTypeCache.GetStructInstanceFields(targetType))
{ {
InheritNullableStateOfMember(targetSlot, valueSlot, field, isDefaultValue: isDefaultValue, slotWatermark); InheritNullableStateOfMember(targetSlot, valueSlot, field, isDefaultValue: isDefaultValue, skipSlot);
} }
} }
// 'slotWatermark' is used to avoid inheriting members from inherited members. // 'skipSlot' is the original target slot that should be skipped in case of cycles.
private void InheritNullableStateOfMember(int targetContainerSlot, int valueContainerSlot, Symbol member, bool isDefaultValue, int slotWatermark) private void InheritNullableStateOfMember(int targetContainerSlot, int valueContainerSlot, Symbol member, bool isDefaultValue, int skipSlot)
{ {
Debug.Assert(targetContainerSlot > 0); Debug.Assert(targetContainerSlot > 0);
Debug.Assert(valueContainerSlot <= slotWatermark); Debug.Assert(skipSlot > 0);
// https://github.com/dotnet/roslyn/issues/33428: Ensure member is valid for target and value. // https://github.com/dotnet/roslyn/issues/33428: Ensure member is valid for target and value.
TypeSymbolWithAnnotations fieldOrPropertyType = member.GetTypeOrReturnType(); TypeSymbolWithAnnotations fieldOrPropertyType = member.GetTypeOrReturnType();
...@@ -874,12 +872,20 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont ...@@ -874,12 +872,20 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
if (fieldOrPropertyType.IsReferenceType || fieldOrPropertyType.IsPossiblyNullableReferenceTypeTypeParameter() || fieldOrPropertyType.IsNullableType()) if (fieldOrPropertyType.IsReferenceType || fieldOrPropertyType.IsPossiblyNullableReferenceTypeTypeParameter() || fieldOrPropertyType.IsNullableType())
{ {
int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot); int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot);
Debug.Assert(targetMemberSlot > 0);
NullableAnnotation value = (isDefaultValue && fieldOrPropertyType.IsReferenceType) ? NullableAnnotation value = (isDefaultValue && fieldOrPropertyType.IsReferenceType) ?
NullableAnnotation.Nullable : NullableAnnotation.Nullable :
fieldOrPropertyType.NullableAnnotation; fieldOrPropertyType.NullableAnnotation;
int valueMemberSlot = -1;
if (valueContainerSlot > 0) if (valueContainerSlot > 0)
{ {
int valueMemberSlot = VariableSlot(member, valueContainerSlot); valueMemberSlot = VariableSlot(member, valueContainerSlot);
if (valueMemberSlot == skipSlot)
{
return;
}
value = valueMemberSlot > 0 && valueMemberSlot < this.State.Capacity ? value = valueMemberSlot > 0 && valueMemberSlot < this.State.Capacity ?
this.State[valueMemberSlot] : this.State[valueMemberSlot] :
NullableAnnotation.Unknown; NullableAnnotation.Unknown;
...@@ -887,13 +893,9 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont ...@@ -887,13 +893,9 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
this.State[targetMemberSlot] = value; this.State[targetMemberSlot] = value;
if (valueContainerSlot > 0) if (valueMemberSlot > 0)
{ {
int valueMemberSlot = VariableSlot(member, valueContainerSlot); InheritNullableStateOfTrackableType(targetMemberSlot, valueMemberSlot, skipSlot);
if (valueMemberSlot > 0 && valueMemberSlot < slotWatermark)
{
InheritNullableStateOfTrackableType(targetMemberSlot, valueMemberSlot, slotWatermark);
}
} }
} }
else if (EmptyStructTypeCache.IsTrackableStructType(fieldOrPropertyType.TypeSymbol)) else if (EmptyStructTypeCache.IsTrackableStructType(fieldOrPropertyType.TypeSymbol))
...@@ -901,16 +903,12 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont ...@@ -901,16 +903,12 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot); int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot);
if (targetMemberSlot > 0) if (targetMemberSlot > 0)
{ {
int valueMemberSlot = -1; int valueMemberSlot = (valueContainerSlot > 0) ? GetOrCreateSlot(member, valueContainerSlot) : -1;
if (valueContainerSlot > 0) if (valueMemberSlot == skipSlot)
{ {
int slot = GetOrCreateSlot(member, valueContainerSlot); return;
if (slot < slotWatermark)
{
valueMemberSlot = slot;
}
} }
InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, targetMemberSlot, valueMemberSlot, isDefaultValue: isDefaultValue, slotWatermark); InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, targetMemberSlot, valueMemberSlot, isDefaultValue: isDefaultValue, skipSlot);
} }
} }
} }
...@@ -932,13 +930,13 @@ private void InheritDefaultState(int targetSlot) ...@@ -932,13 +930,13 @@ private void InheritDefaultState(int targetSlot)
} }
} }
private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot, int slotWatermark) private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot, int skipSlot)
{ {
Debug.Assert(targetSlot > 0); Debug.Assert(targetSlot > 0);
Debug.Assert(valueSlot > 0); Debug.Assert(valueSlot > 0);
// Clone the state for members that have been set on the value. // Clone the state for members that have been set on the value.
for (int slot = valueSlot + 1; slot < slotWatermark; slot++) for (int slot = valueSlot + 1; slot < nextVariableSlot; slot++)
{ {
var variable = variableBySlot[slot]; var variable = variableBySlot[slot];
if (variable.ContainingSlot != valueSlot) if (variable.ContainingSlot != valueSlot)
...@@ -947,7 +945,7 @@ private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot, ...@@ -947,7 +945,7 @@ private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot,
} }
var member = variable.Symbol; var member = variable.Symbol;
Debug.Assert(member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property || member.Kind == SymbolKind.Event); Debug.Assert(member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property || member.Kind == SymbolKind.Event);
InheritNullableStateOfMember(targetSlot, valueSlot, member, isDefaultValue: false, slotWatermark); InheritNullableStateOfMember(targetSlot, valueSlot, member, isDefaultValue: false, skipSlot);
} }
} }
...@@ -1004,8 +1002,7 @@ private void EnterParameter(ParameterSymbol parameter, TypeSymbolWithAnnotations ...@@ -1004,8 +1002,7 @@ private void EnterParameter(ParameterSymbol parameter, TypeSymbolWithAnnotations
parameterType.TypeSymbol, parameterType.TypeSymbol,
slot, slot,
valueSlot: -1, valueSlot: -1,
isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true, isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true);
slotWatermark: GetSlotWatermark());
} }
} }
} }
...@@ -1293,8 +1290,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre ...@@ -1293,8 +1290,7 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre
type, type,
slot, slot,
valueSlot: -1, valueSlot: -1,
isDefaultValue: isDefaultValueTypeConstructor, isDefaultValue: isDefaultValueTypeConstructor);
slotWatermark: GetSlotWatermark());
} }
} }
} }
...@@ -4227,11 +4223,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) ...@@ -4227,11 +4223,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
else else
{ {
TypeSymbolWithAnnotations rightType = VisitOptionalImplicitConversion(right, leftType, UseLegacyWarnings(left), AssignmentKind.Assignment); TypeSymbolWithAnnotations rightType = VisitOptionalImplicitConversion(right, leftType, UseLegacyWarnings(left), AssignmentKind.Assignment);
int rightSlot = MakeSlot(right); TrackNullableStateForAssignment(right, leftType, MakeSlot(left), rightType, MakeSlot(right));
int slotWatermark = GetSlotWatermark();
// leftSlot is calculated last to avoid copying from that slot in cycles.
int leftSlot = MakeSlot(left);
TrackNullableStateForAssignment(right, leftType, leftSlot, rightType, rightSlot, slotWatermark);
// https://github.com/dotnet/roslyn/issues/30066 Check node.Type.IsErrorType() instead? // https://github.com/dotnet/roslyn/issues/30066 Check node.Type.IsErrorType() instead?
_resultType = node.HasErrors ? TypeSymbolWithAnnotations.Create(node.Type) : rightType; _resultType = node.HasErrors ? TypeSymbolWithAnnotations.Create(node.Type) : rightType;
} }
...@@ -4336,21 +4328,58 @@ private void VisitDeconstructionArguments(ArrayBuilder<DeconstructionVariable> v ...@@ -4336,21 +4328,58 @@ private void VisitDeconstructionArguments(ArrayBuilder<DeconstructionVariable> v
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
{ {
var variable = variables[i]; var variable = variables[i];
var underlyingConversion = conversion.UnderlyingConversions[i];
var rightPart = rightParts[i]; var rightPart = rightParts[i];
var nestedVariables = variable.NestedVariables; var nestedVariables = variable.NestedVariables;
if (nestedVariables != null) if (nestedVariables != null)
{ {
VisitDeconstructionArguments(nestedVariables, conversion.UnderlyingConversions[i], rightPart); VisitDeconstructionArguments(nestedVariables, underlyingConversion, rightPart);
} }
else else
{ {
var targetType = variable.Type; var targetType = variable.Type;
var valueType = VisitOptionalImplicitConversion(rightPart, targetType, useLegacyWarnings: true, AssignmentKind.Assignment); TypeSymbolWithAnnotations operandType;
int valueSlot = MakeSlot(rightPart); TypeSymbolWithAnnotations valueType;
int slotWatermark = GetSlotWatermark(); int valueSlot;
// targetSlot is calculated last to avoid copying from that slot in cycles. if (underlyingConversion.IsIdentity)
{
operandType = default;
valueType = VisitOptionalImplicitConversion(rightPart, targetType, useLegacyWarnings: true, AssignmentKind.Assignment);
valueSlot = MakeSlot(rightPart);
}
else
{
operandType = VisitRvalueWithResult(rightPart);
valueType = ApplyConversion(
rightPart,
rightPart,
underlyingConversion,
targetType,
operandType,
checkConversion: true,
fromExplicitCast: false,
useLegacyWarnings: true,
AssignmentKind.Assignment,
reportTopLevelWarnings: true,
reportRemainingWarnings: true);
valueSlot = -1;
}
int targetSlot = MakeSlot(variable.Expression); int targetSlot = MakeSlot(variable.Expression);
TrackNullableStateForAssignment(rightPart, targetType, targetSlot, valueType, valueSlot, slotWatermark); TrackNullableStateForAssignment(rightPart, targetType, targetSlot, valueType, valueSlot);
// Conversion of T to Nullable<T> is equivalent to new Nullable<T>(t).
// (Should this check be moved to VisitOptionalImplicitConversion or TrackNullableStateForAssignment?)
if (targetSlot > 0 &&
underlyingConversion.Kind == ConversionKind.ImplicitNullable &&
AreNullableAndUnderlyingTypes(targetType.TypeSymbol, operandType.TypeSymbol, out TypeSymbolWithAnnotations underlyingType))
{
valueSlot = MakeSlot(rightPart);
if (valueSlot > 0)
{
TrackNullableStateOfNullableValue(targetSlot, targetType.TypeSymbol, rightPart, underlyingType, valueSlot);
}
}
} }
} }
} }
...@@ -5085,7 +5114,7 @@ public override BoundNode VisitDefaultExpression(BoundDefaultExpression node) ...@@ -5085,7 +5114,7 @@ public override BoundNode VisitDefaultExpression(BoundDefaultExpression node)
if (slot > 0) if (slot > 0)
{ {
this.State[slot] = NullableAnnotation.NotNullable; this.State[slot] = NullableAnnotation.NotNullable;
InheritNullableStateOfTrackableStruct(type, slot, valueSlot: -1, isDefaultValue: true, slotWatermark: GetSlotWatermark()); InheritNullableStateOfTrackableStruct(type, slot, valueSlot: -1, isDefaultValue: true);
} }
} }
_resultType = TypeSymbolWithAnnotations.Create(type, (type is null || type.IsNullableType() || !type.IsValueType) ? NullableAnnotation.Nullable : NullableAnnotation.Unknown); _resultType = TypeSymbolWithAnnotations.Create(type, (type is null || type.IsNullableType() || !type.IsValueType) ? NullableAnnotation.Nullable : NullableAnnotation.Unknown);
......
...@@ -49170,6 +49170,34 @@ static void M(C x) ...@@ -49170,6 +49170,34 @@ static void M(C x)
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x.F.F").WithLocation(7, 9)); Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x.F.F").WithLocation(7, 9));
} }
[Fact]
public void Members_FieldCycle_06()
{
var source =
@"#pragma warning disable 0649
class A
{
internal B B = default!;
}
class B
{
internal A? A;
}
class Program
{
static void M(A a)
{
a.B.A = a;
a.B.A.B.A.ToString();
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (15,9): warning CS8602: Possible dereference of a null reference.
// a.B.A.B.A.ToString();
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a.B.A.B.A").WithLocation(15, 9));
}
[Fact] [Fact]
public void Members_FieldCycle_Struct() public void Members_FieldCycle_Struct()
{ {
...@@ -79477,14 +79505,42 @@ static void F(S s) ...@@ -79477,14 +79505,42 @@ static void F(S s)
} }
}"; }";
var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); var comp = CreateCompilation(source, options: WithNonNullTypesTrue());
// https://github.com/dotnet/roslyn/issues/33011: Should warn for `y.Value.F` only.
comp.VerifyDiagnostics( comp.VerifyDiagnostics(
// (12,13): warning CS8629: Nullable value type may be null. // (15,9): warning CS8602: Possible dereference of a null reference.
// _ = x.Value; // y.Value.F.ToString(); // 1
Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "x.Value").WithLocation(12, 13), Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y.Value.F").WithLocation(15, 9));
// (14,13): warning CS8629: Nullable value type may be null. }
// _ = y.Value;
Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "y.Value").WithLocation(14, 13)); [Fact]
[WorkItem(33011, "https://github.com/dotnet/roslyn/issues/33011")]
public void Deconstruction_ImplicitNullableConversion_03()
{
var source =
@"#pragma warning disable 0649
struct S<T>
{
internal T F;
}
class Program
{
static void F<T>((S<T?>, S<T>) t) where T : class, new()
{
(S<T>? x, S<T?>? y) = t; // 1, 2
x.Value.F.ToString(); // 3
y.Value.F.ToString();
}
}";
var comp = CreateCompilation(source, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (10,31): warning CS8619: Nullability of reference types in value of type 'S<T?>' doesn't match target type 'S<T>?'.
// (S<T>? x, S<T?>? y) = t; // 1, 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "t").WithArguments("S<T?>", "S<T>?").WithLocation(10, 31),
// (10,31): warning CS8619: Nullability of reference types in value of type 'S<T>' doesn't match target type 'S<T?>?'.
// (S<T>? x, S<T?>? y) = t; // 1, 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "t").WithArguments("S<T>", "S<T?>?").WithLocation(10, 31),
// (11,9): warning CS8602: Possible dereference of a null reference.
// x.Value.F.ToString(); // 3
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x.Value.F").WithLocation(11, 9));
} }
[Fact] [Fact]
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册