Skip to content

Commit 3cef228

Browse files
authored
var should infer nullable type (#40755)
1 parent fd6afb2 commit 3cef228

File tree

12 files changed

+937
-397
lines changed

12 files changed

+937
-397
lines changed

docs/features/nullable-reference-types.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ Nullablilty follows from assignment above. Assigning `?` to `!` is a W warning.
146146
```c#
147147
string notNull = maybeNull; // assigns ?, warning
148148
```
149-
Nullability of `var` declarations is determined from flow analysis.
149+
The nullability of `var` declarations is the nullable version of the inferred type.
150150
```c#
151151
var s = maybeNull; // s is ?, no warning
152152
if (maybeNull == null) return;
153-
var t = maybeNull; // t is !
153+
var t = maybeNull; // t is ? too
154154
```
155155

156156
### Suppression operator (`!`)

src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ private void BindPatternDesignation(
438438

439439
variableSymbol = localSymbol;
440440
variableAccess = new BoundLocal(
441-
syntax: designation, localSymbol: localSymbol, constantValueOpt: null, type: declType.Type);
441+
syntax: designation, localSymbol: localSymbol, localSymbol.IsVar ? BoundLocalDeclarationKind.WithInferredType : BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.Type);
442442
return;
443443
}
444444
else

src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,12 +1824,12 @@ public override BoundNode VisitLocalDeclaration(BoundLocalDeclaration node)
18241824
valueType = type.ToTypeWithState();
18251825
}
18261826

1827-
type = valueType.ToTypeWithAnnotations();
1827+
type = valueType.ToAnnotatedTypeWithAnnotations();
18281828
_variableTypes[local] = type;
18291829

18301830
if (node.DeclaredTypeOpt != null)
18311831
{
1832-
SetAnalyzedNullability(node.DeclaredTypeOpt, new VisitResult(valueType, type), true);
1832+
SetAnalyzedNullability(node.DeclaredTypeOpt, new VisitResult(type.ToTypeWithState(), type), true);
18331833
}
18341834
}
18351835

@@ -4122,7 +4122,7 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions(
41224122
var lValueType = ApplyLValueAnnotations(declaredType, leftAnnotations);
41234123
if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
41244124
{
4125-
var varType = worstCaseParameterWithState.ToTypeWithAnnotations();
4125+
var varType = worstCaseParameterWithState.ToAnnotatedTypeWithAnnotations();
41264126
_variableTypes[local.LocalSymbol] = varType;
41274127
lValueType = varType;
41284128
}
@@ -6440,7 +6440,7 @@ private void VisitTupleDeconstructionArguments(ArrayBuilder<DeconstructionVariab
64406440
{
64416441
// when the LHS is a var declaration, we can just visit the right part to infer the type
64426442
valueType = operandType = VisitRvalueWithState(rightPart);
6443-
_variableTypes[variable.Expression.ExpressionSymbol] = operandType.ToTypeWithAnnotations();
6443+
_variableTypes[variable.Expression.ExpressionSymbol] = operandType.ToAnnotatedTypeWithAnnotations();
64446444
}
64456445
else
64466446
{
@@ -7153,6 +7153,7 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
71537153
{
71547154
TypeWithAnnotations destinationType = iterationVariable.TypeWithAnnotations;
71557155
TypeWithState result = sourceState;
7156+
TypeWithState resultForType = sourceState;
71567157
if (iterationVariable.IsRef)
71577158
{
71587159
// foreach (ref DestinationType variable in collection)
@@ -7165,7 +7166,9 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
71657166
else if (node.Syntax is ForEachStatementSyntax { Type: { IsVar: true } })
71667167
{
71677168
// foreach (var variable in collection)
7168-
_variableTypes[iterationVariable] = sourceState.ToTypeWithAnnotations();
7169+
destinationType = sourceState.ToAnnotatedTypeWithAnnotations();
7170+
_variableTypes[iterationVariable] = destinationType;
7171+
resultForType = destinationType.ToTypeWithState();
71697172
}
71707173
else
71717174
{
@@ -7191,7 +7194,7 @@ public override void VisitForEachIterationVariables(BoundForEachStatement node)
71917194
}
71927195

71937196
// 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
7194-
SetAnalyzedNullability(node.IterationVariableType, new VisitResult(result, destinationType), isLvalue: true);
7197+
SetAnalyzedNullability(node.IterationVariableType, new VisitResult(resultForType, destinationType), isLvalue: true);
71957198
state = result.State;
71967199
}
71977200

src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,17 +402,18 @@ protected override void VisitSwitchSection(BoundSwitchSection node, bool isLastS
402402
Debug.Assert(foundTemp);
403403
var (tempSlot, tempType) = tempSlotAndType;
404404
var tempState = this.State[tempSlot];
405-
if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol local })
405+
if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol local } boundLocal)
406406
{
407-
var inferredType = TypeWithState.Create(tempType, tempState).ToTypeWithAnnotations();
407+
var value = TypeWithState.Create(tempType, tempState);
408+
var type = boundLocal.DeclarationKind == BoundLocalDeclarationKind.WithInferredType ? value.ToAnnotatedTypeWithAnnotations() : value.ToTypeWithAnnotations();
408409
if (_variableTypes.TryGetValue(local, out var existingType))
409410
{
410411
// merge inferred nullable annotation from different branches of the decision tree
411-
_variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation));
412+
_variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(type.NullableAnnotation));
412413
}
413414
else
414415
{
415-
_variableTypes[local] = inferredType;
416+
_variableTypes[local] = type;
416417
}
417418

418419
int localSlot = GetOrCreateSlot(local, forceSlotEvenIfEmpty: true);

src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,12 @@ public TypeWithAnnotations ToTypeWithAnnotations()
8686
? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated;
8787
return TypeWithAnnotations.Create(this.Type, annotation);
8888
}
89+
90+
public TypeWithAnnotations ToAnnotatedTypeWithAnnotations()
91+
{
92+
NullableAnnotation annotation = Type?.IsTypeParameterDisallowingAnnotation() == true
93+
? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated;
94+
return TypeWithAnnotations.Create(this.Type, annotation);
95+
}
8996
}
9097
}

0 commit comments

Comments
 (0)