Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/features/nullable-reference-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ Nullablilty follows from assignment above. Assigning `?` to `!` is a W warning.
```c#
string notNull = maybeNull; // assigns ?, warning
```
Nullability of `var` declarations is determined from flow analysis.
The nullability of `var` declarations is the nullable version of the inferred type.
```c#
var s = maybeNull; // s is ?, no warning
if (maybeNull == null) return;
var t = maybeNull; // t is !
var t = maybeNull; // t is ? too
```

### Suppression operator (`!`)
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ private void BindPatternDesignation(

variableSymbol = localSymbol;
variableAccess = new BoundLocal(
syntax: designation, localSymbol: localSymbol, constantValueOpt: null, type: declType.Type);
syntax: designation, localSymbol: localSymbol, localSymbol.IsVar ? BoundLocalDeclarationKind.WithInferredType : BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.Type);
return;
}
else
Expand Down
15 changes: 9 additions & 6 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1824,12 +1824,12 @@ public override BoundNode VisitLocalDeclaration(BoundLocalDeclaration node)
valueType = type.ToTypeWithState();
}

type = valueType.ToTypeWithAnnotations();
type = valueType.ToAnnotatedTypeWithAnnotations();
_variableTypes[local] = type;

if (node.DeclaredTypeOpt != null)
{
SetAnalyzedNullability(node.DeclaredTypeOpt, new VisitResult(valueType, type), true);
SetAnalyzedNullability(node.DeclaredTypeOpt, new VisitResult(type.ToTypeWithState(), type), true);
}
}

Expand Down Expand Up @@ -4122,7 +4122,7 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions(
var lValueType = ApplyLValueAnnotations(declaredType, leftAnnotations);
if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
{
var varType = worstCaseParameterWithState.ToTypeWithAnnotations();
var varType = worstCaseParameterWithState.ToAnnotatedTypeWithAnnotations();
_variableTypes[local.LocalSymbol] = varType;
lValueType = varType;
}
Expand Down Expand Up @@ -6440,7 +6440,7 @@ private void VisitTupleDeconstructionArguments(ArrayBuilder<DeconstructionVariab
{
// when the LHS is a var declaration, we can just visit the right part to infer the type
valueType = operandType = VisitRvalueWithState(rightPart);
_variableTypes[variable.Expression.ExpressionSymbol] = operandType.ToTypeWithAnnotations();
_variableTypes[variable.Expression.ExpressionSymbol] = operandType.ToAnnotatedTypeWithAnnotations();
}
else
{
Expand Down Expand Up @@ -7153,6 +7153,7 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
{
TypeWithAnnotations destinationType = iterationVariable.TypeWithAnnotations;
TypeWithState result = sourceState;
TypeWithState resultForType = sourceState;
if (iterationVariable.IsRef)
{
// foreach (ref DestinationType variable in collection)
Expand All @@ -7165,7 +7166,9 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
else if (node.Syntax is ForEachStatementSyntax { Type: { IsVar: true } })
{
// foreach (var variable in collection)
_variableTypes[iterationVariable] = sourceState.ToTypeWithAnnotations();
destinationType = sourceState.ToAnnotatedTypeWithAnnotations();
_variableTypes[iterationVariable] = destinationType;
resultForType = destinationType.ToTypeWithState();
}
else
{
Expand All @@ -7191,7 +7194,7 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
}

// In non-error cases we'll only run this loop a single time. In error cases we'll set the nullability of the VariableType multiple times, but at least end up with something
SetAnalyzedNullability(node.IterationVariableType, new VisitResult(result, destinationType), isLvalue: true);
SetAnalyzedNullability(node.IterationVariableType, new VisitResult(resultForType, destinationType), isLvalue: true);
state = result.State;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,17 +402,18 @@ protected override void VisitSwitchSection(BoundSwitchSection node, bool isLastS
Debug.Assert(foundTemp);
var (tempSlot, tempType) = tempSlotAndType;
var tempState = this.State[tempSlot];
if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol local })
if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol local } boundLocal)
{
var inferredType = TypeWithState.Create(tempType, tempState).ToTypeWithAnnotations();
var value = TypeWithState.Create(tempType, tempState);
var type = boundLocal.DeclarationKind == BoundLocalDeclarationKind.WithInferredType ? value.ToAnnotatedTypeWithAnnotations() : value.ToTypeWithAnnotations();
if (_variableTypes.TryGetValue(local, out var existingType))
{
// merge inferred nullable annotation from different branches of the decision tree
_variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation));
_variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(type.NullableAnnotation));
Copy link
Member

@gafter gafter Jan 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existingType [](start = 87, length = 12)

I think this should be type, since we have a new inferred type. #WontFix

}
else
{
_variableTypes[local] = inferredType;
_variableTypes[local] = type;
}
Copy link
Member

@gafter gafter Jan 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be done for variables declared with a var pattern. The others should stay non-null. #Resolved


int localSlot = GetOrCreateSlot(local, forceSlotEvenIfEmpty: true);
Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,12 @@ public TypeWithAnnotations ToTypeWithAnnotations()
? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated;
return TypeWithAnnotations.Create(this.Type, annotation);
}

public TypeWithAnnotations ToAnnotatedTypeWithAnnotations()
{
NullableAnnotation annotation = Type?.IsTypeParameterDisallowingAnnotation() == true
? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated;
return TypeWithAnnotations.Create(this.Type, annotation);
}
}
}
Loading