Permalink
Browse files

Non-nullable fields of default struct value should be treated as null…

…able (#32304)
  • Loading branch information...
cston committed Jan 11, 2019
1 parent 00cbaf0 commit a2d838ec736b72cff262c6107f7265f88e1d453b
@@ -94,14 +94,25 @@ Invocation of methods annotated with the following attributes will also affect f
- `[AssertsTrue]` (e.g. `Debug.Assert`) and `[AssertsFalse]`

## `default`
`default(T)` is `T?` if `T` is a reference type.
_Is `default(T)` also `T?` if `T` is an unconstrained type parameter?_
_Is `default(T?)` an error?_
If `T` is a reference type, `default(T)` is `T?`.
```c#
string? s = default(string); // assigns ?, no warning
string t = default; // assigns ?, warning
T t = default; // assigns ?, warning
```
If `T` is a value type, `default(T)` is `T` and any non-value-type fields in `T` are maybe null.
If `T` is a value type, `new T()` is equivalent to `default(T)`.
```c#
struct Pair<T, U> { public T First; public U Second; }
var p = default(Pair<object?, string>); // ok: Pair<object?, string!> p
p.Second.ToString(); // warning
(object?, string) t = default; // ok
t.Item2.ToString(); // warning
```
If `T` is an unconstrained type parameter, `default(T)` is a `T` that is maybe null.
```c#
T t = default; // warning
```
_Is `default(T?)` an error?_

### Conversions
Conversions can be calculated with ~ considered distinct from ? and !, or with ~ implicitly convertible to ? and !.
@@ -8,7 +8,6 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class LocalDataFlowPass<TLocalState>
{
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
internal struct VariableIdentifier : IEquatable<VariableIdentifier>
{
public readonly Symbol Symbol;
@@ -49,7 +48,7 @@ public override bool Equals(object obj)
return !left.Equals(right);
}

internal string GetDebuggerDisplay()
public override string ToString()
{
return $"ContainingSlot={ContainingSlot}, Symbol={Symbol.GetDebuggerDisplay()}";
}
@@ -436,6 +436,7 @@ protected override int MakeSlot(BoundExpression node)
}
}
break;
case BoundKind.DefaultExpression:
case BoundKind.ObjectCreationExpression:
case BoundKind.DynamicObjectCreationExpression:
case BoundKind.AnonymousObjectCreationExpression:
@@ -445,15 +446,16 @@ protected override int MakeSlot(BoundExpression node)
}
break;
case BoundKind.ConditionalReceiver:
if (_lastConditionalAccessSlot != -1)
{
int slot = _lastConditionalAccessSlot;
_lastConditionalAccessSlot = -1;
return slot;
}
break;
default:
return base.MakeSlot(node);
}
return base.MakeSlot(node);

return -1;

MethodSymbol getTopLevelMethod(MethodSymbol method)
{
@@ -587,7 +589,7 @@ private bool ReportNullableAssignmentIfNecessary(BoundExpression value, TypeSymb
// target (e.g.: `object x = null;` or calling `void F(object y)` with `F(null)`).
bool reportNullLiteralAssignmentIfNecessary(BoundExpression expr)
{
if (expr.ConstantValue?.IsNull != true && !isDefaultOfUnconstrainedTypeParameter(expr))
if (expr.ConstantValue?.IsNull != true && !IsDefaultOfUnconstrainedTypeParameter(expr))
{
return false;
}
@@ -602,22 +604,39 @@ bool reportNullLiteralAssignmentIfNecessary(BoundExpression expr)
}
return true;
}
}

bool isDefaultOfUnconstrainedTypeParameter(BoundExpression expr)
private static bool IsDefaultOfUnconstrainedTypeParameter(BoundExpression expr)
{
switch (expr.Kind)
{
switch (expr.Kind)
{
case BoundKind.Conversion:
{
var conversion = (BoundConversion)expr;
return conversion.Conversion.Kind == ConversionKind.DefaultOrNullLiteral &&
isDefaultOfUnconstrainedTypeParameter(conversion.Operand);
}
case BoundKind.DefaultExpression:
return IsTypeParameterDisallowingAnnotation(expr.Type);
default:
return false;
}
case BoundKind.Conversion:
{
var conversion = (BoundConversion)expr;
return conversion.Conversion.Kind == ConversionKind.DefaultOrNullLiteral &&
IsDefaultOfUnconstrainedTypeParameter(conversion.Operand);
}
case BoundKind.DefaultExpression:
return IsTypeParameterDisallowingAnnotation(expr.Type);
default:
return false;
}
}

private static bool IsDefaultValue(BoundExpression expr)
{
switch (expr.Kind)
{
case BoundKind.Conversion:
{
var conversion = (BoundConversion)expr;
return conversion.Conversion.Kind == ConversionKind.DefaultOrNullLiteral &&
IsDefaultValue(conversion.Operand);
}
case BoundKind.DefaultExpression:
return true;
default:
return false;
}
}

@@ -709,9 +728,9 @@ private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWi
}
}
else if (EmptyStructTypeCache.IsTrackableStructType(targetType.TypeSymbol) &&
targetType.TypeSymbol.Equals(valueType.TypeSymbol, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
targetType.TypeSymbol.Equals(valueType.TypeSymbol, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
{
InheritNullableStateOfTrackableStruct(targetType.TypeSymbol, targetSlot, valueSlot, IsByRefTarget(targetSlot), slotWatermark: GetSlotWatermark());
InheritNullableStateOfTrackableStruct(targetType.TypeSymbol, targetSlot, valueSlot, isDefaultValue: IsDefaultValue(value), isByRefTarget: IsByRefTarget(targetSlot), slotWatermark: GetSlotWatermark());
}
}
}
@@ -771,7 +790,7 @@ private void ReportDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params
}
}

private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isByRefTarget, int slotWatermark)
private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isDefaultValue, bool isByRefTarget, int slotWatermark)
{
Debug.Assert(targetSlot > 0);
Debug.Assert(EmptyStructTypeCache.IsTrackableStructType(targetType));
@@ -780,13 +799,14 @@ private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int ta
// See ModifyMembers_StructPropertyNoBackingField and PropertyCycle_Struct tests.
foreach (var field in _emptyStructTypeCache.GetStructInstanceFields(targetType))
{
InheritNullableStateOfMember(targetSlot, valueSlot, field, isByRefTarget, slotWatermark);
InheritNullableStateOfMember(targetSlot, valueSlot, field, isDefaultValue: isDefaultValue, isByRefTarget: isByRefTarget, slotWatermark);
}
}

// 'slotWatermark' is used to avoid inheriting members from inherited members.
private void InheritNullableStateOfMember(int targetContainerSlot, int valueContainerSlot, Symbol member, bool isByRefTarget, int slotWatermark)
private void InheritNullableStateOfMember(int targetContainerSlot, int valueContainerSlot, Symbol member, bool isDefaultValue, bool isByRefTarget, int slotWatermark)
{
Debug.Assert(targetContainerSlot > 0);
Debug.Assert(valueContainerSlot <= slotWatermark);

TypeSymbolWithAnnotations fieldOrPropertyType = member.GetTypeOrReturnType();
@@ -797,7 +817,9 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
if (fieldOrPropertyType.IsReferenceType || fieldOrPropertyType.IsNullableType())
{
int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot);
NullableAnnotation value = fieldOrPropertyType.NullableAnnotation;
NullableAnnotation value = (isDefaultValue && fieldOrPropertyType.IsReferenceType) ?
NullableAnnotation.Nullable :
fieldOrPropertyType.NullableAnnotation;
// https://github.com/dotnet/roslyn/issues/29968 Remove isByRefTarget check?
if (isByRefTarget)
{
@@ -838,7 +860,7 @@ private void InheritNullableStateOfMember(int targetContainerSlot, int valueCont
valueMemberSlot = slot;
}
}
InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, targetMemberSlot, valueMemberSlot, isByRefTarget, slotWatermark);
InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, targetMemberSlot, valueMemberSlot, isDefaultValue: isDefaultValue, isByRefTarget: isByRefTarget, slotWatermark);
}
}
}
@@ -875,7 +897,7 @@ private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot,
}
var member = variable.Symbol;
Debug.Assert(member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property || member.Kind == SymbolKind.Event);
InheritNullableStateOfMember(targetSlot, valueSlot, member, isByRefTarget, slotWatermark);
InheritNullableStateOfMember(targetSlot, valueSlot, member, isDefaultValue: false, isByRefTarget, slotWatermark);
}
}

@@ -928,13 +950,17 @@ private void EnterParameter(ParameterSymbol parameter, TypeSymbolWithAnnotations
{
if (EmptyStructTypeCache.IsTrackableStructType(parameterType.TypeSymbol))
{
InheritNullableStateOfTrackableStruct(parameterType.TypeSymbol, slot, valueSlot: -1, isByRefTarget: parameter.RefKind != RefKind.None, slotWatermark: GetSlotWatermark());
InheritNullableStateOfTrackableStruct(
parameterType.TypeSymbol,
slot,
valueSlot: -1,
isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true,
isByRefTarget: parameter.RefKind != RefKind.None,
slotWatermark: GetSlotWatermark());
}
}
}

#region Visitors

public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node)
{
VisitRvalue(node.Expression);
@@ -1182,27 +1208,31 @@ private void VisitObjectOrDynamicObjectCreation(BoundExpression node, BoundExpre
{
Debug.Assert(node.Kind == BoundKind.ObjectCreationExpression || node.Kind == BoundKind.DynamicObjectCreationExpression);

LocalSymbol receiver = null;
int slot = -1;
TypeSymbol type = node.Type;
if ((object)type != null)
{
bool isTrackableStructType = EmptyStructTypeCache.IsTrackableStructType(type);
if (!type.IsValueType || isTrackableStructType)
{
receiver = GetOrCreateObjectCreationPlaceholder(node);
slot = GetOrCreateSlot(receiver);
slot = GetOrCreateObjectCreationPlaceholderSlot(node);
if (slot > 0 && isTrackableStructType)
{
this.State[slot] = NullableAnnotation.NotNullable;
InheritNullableStateOfTrackableStruct(type, slot, valueSlot: -1, isByRefTarget: false, slotWatermark: GetSlotWatermark());
InheritNullableStateOfTrackableStruct(
type,
slot,
valueSlot: -1,
isDefaultValue: (node as BoundObjectCreationExpression)?.Constructor.IsDefaultValueTypeConstructor() == true,
isByRefTarget: false,
slotWatermark: GetSlotWatermark());
}
}
}

if (initializerOpt != null)
{
VisitObjectCreationInitializer(receiver, slot, initializerOpt);
VisitObjectCreationInitializer(null, slot, initializerOpt);
}

_resultType = TypeSymbolWithAnnotations.Create(type, NullableAnnotation.NotNullable);
@@ -1218,7 +1248,7 @@ private void VisitObjectCreationInitializer(Symbol containingSymbol, int contain
switch (initializer.Kind)
{
case BoundKind.AssignmentOperator:
VisitObjectElementInitializer(containingSymbol, containingSlot, (BoundAssignmentOperator)initializer);
VisitObjectElementInitializer(containingSlot, (BoundAssignmentOperator)initializer);
break;
default:
VisitRvalue(initializer);
@@ -1242,6 +1272,7 @@ private void VisitObjectCreationInitializer(Symbol containingSymbol, int contain
break;
default:
TypeSymbolWithAnnotations resultType = VisitRvalueWithResult(node);
Debug.Assert((object)containingSymbol != null);
if ((object)containingSymbol != null)
{
var type = containingSymbol.GetTypeOrReturnType();
@@ -1252,7 +1283,7 @@ private void VisitObjectCreationInitializer(Symbol containingSymbol, int contain
}
}

private void VisitObjectElementInitializer(Symbol containingSymbol, int containingSlot, BoundAssignmentOperator node)
private void VisitObjectElementInitializer(int containingSlot, BoundAssignmentOperator node)
{
var left = node.Left;
switch (left.Kind)
@@ -1290,7 +1321,7 @@ private void SetResult(BoundExpression node)
_resultType = TypeSymbolWithAnnotations.Create(node.Type);
}

private ObjectCreationPlaceholderLocal GetOrCreateObjectCreationPlaceholder(BoundExpression node)
private int GetOrCreateObjectCreationPlaceholderSlot(BoundExpression node)
{
ObjectCreationPlaceholderLocal placeholder;
if (_placeholderLocalsOpt == null)
@@ -1309,7 +1340,7 @@ private ObjectCreationPlaceholderLocal GetOrCreateObjectCreationPlaceholder(Boun
_placeholderLocalsOpt.Add(node, placeholder);
}

return placeholder;
return GetOrCreateSlot(placeholder);
}

public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousObjectCreationExpression node)
@@ -1337,8 +1368,7 @@ public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousO
PropertySymbol property = node.Declarations[i].Property;
if (receiverSlot <= 0)
{
ObjectCreationPlaceholderLocal implicitReceiver = GetOrCreateObjectCreationPlaceholder(node);
receiverSlot = GetOrCreateSlot(implicitReceiver);
receiverSlot = GetOrCreateObjectCreationPlaceholderSlot(node);
}

TypeSymbolWithAnnotations propertyType = property.Type;
@@ -3558,7 +3588,7 @@ private bool HasTopLevelNullabilityConversion(TypeSymbolWithAnnotations source,

// conversion "from" type -> method parameter type
NullableAnnotation operandAnnotation = operandType.NullableAnnotation;
operandType = ClassifyAndApplyConversion(operandOpt ?? node, parameterType, isLiftedConversion ? underlyingOperandType : operandType,
_ = ClassifyAndApplyConversion(operandOpt ?? node, parameterType, isLiftedConversion ? underlyingOperandType : operandType,
useLegacyWarnings, AssignmentKind.Argument, target: parameter, reportWarnings: reportRemainingWarnings);

// method parameter type -> method return type
@@ -4638,8 +4668,19 @@ public override BoundNode VisitFieldInfo(BoundFieldInfo node)

public override BoundNode VisitDefaultExpression(BoundDefaultExpression node)
{
Debug.Assert(!this.IsConditionalState);

var result = base.VisitDefaultExpression(node);
TypeSymbol type = node.Type;
if (EmptyStructTypeCache.IsTrackableStructType(type))
{
int slot = GetOrCreateObjectCreationPlaceholderSlot(node);
if (slot > 0)
{
this.State[slot] = NullableAnnotation.NotNullable;
InheritNullableStateOfTrackableStruct(type, slot, valueSlot: -1, isDefaultValue: true, isByRefTarget: false, slotWatermark: GetSlotWatermark());
}
}
_resultType = TypeSymbolWithAnnotations.Create(type, (type is null || type.IsNullableType() || !type.IsValueType) ? NullableAnnotation.Nullable : NullableAnnotation.Unknown);
return result;
}
@@ -5150,8 +5191,6 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no
return null;
}

#endregion Visitors

protected override string Dump(LocalState state)
{
return string.Empty;
Oops, something went wrong.

0 comments on commit a2d838e

Please sign in to comment.