Skip to content

Commit e041433

Browse files
authored
Fix nested nullability of collection expressions (#74490)
1 parent 5a52fdb commit e041433

File tree

4 files changed

+334
-16
lines changed

4 files changed

+334
-16
lines changed

src/Compilers/CSharp/Portable/Binder/Semantics/BestTypeInferrer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public static NullableFlowState GetNullableState(ArrayBuilder<TypeWithState> typ
6969
HashSet<TypeSymbol> candidateTypes = new HashSet<TypeSymbol>(comparer);
7070
foreach (BoundExpression expr in exprs)
7171
{
72+
Debug.Assert(!NullableWalker.IsTargetTypedExpression(expr));
7273
TypeSymbol? type = expr.GetTypeOrFunctionType();
7374

7475
if (type is { })
@@ -132,9 +133,9 @@ public static NullableFlowState GetNullableState(ArrayBuilder<TypeWithState> typ
132133
try
133134
{
134135
var conversionsWithoutNullability = conversions.WithNullability(false);
135-
TypeSymbol? type1 = expr1.Type;
136136

137-
if (type1 is { })
137+
Debug.Assert(!NullableWalker.IsTargetTypedExpression(expr1));
138+
if (expr1.Type is { } type1)
138139
{
139140
if (type1.IsErrorType())
140141
{
@@ -148,9 +149,8 @@ public static NullableFlowState GetNullableState(ArrayBuilder<TypeWithState> typ
148149
}
149150
}
150151

151-
TypeSymbol? type2 = expr2.Type;
152-
153-
if (type2 is { })
152+
Debug.Assert(!NullableWalker.IsTargetTypedExpression(expr2));
153+
if (expr2.Type is { } type2)
154154
{
155155
if (type2.IsErrorType())
156156
{

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3702,7 +3702,7 @@ TypeWithState convertCollection(BoundCollectionExpression node, TypeWithAnnotati
37023702

37033703
// Record the final state
37043704
NullableFlowState resultState = getResultState(node, collectionKind);
3705-
var resultTypeWithState = TypeWithState.Create(node.Type, resultState);
3705+
var resultTypeWithState = TypeWithState.Create(strippedTargetCollectionType, resultState);
37063706
SetAnalyzedNullability(node, resultTypeWithState);
37073707
return resultTypeWithState;
37083708
}
@@ -4570,7 +4570,7 @@ static TypeSymbol setSpanElementType(NamedTypeSymbol namedType, TypeWithAnnotati
45704570
/// This is done using <see cref="TargetTypedAnalysisCompletion"/>. All registered completions must be processed
45714571
/// (ie. analyzed via some conversion) before the nullable analysis completes.
45724572
/// </summary>
4573-
private static bool IsTargetTypedExpression(BoundExpression node)
4573+
internal static bool IsTargetTypedExpression(BoundExpression node)
45744574
{
45754575
return node is BoundConditionalOperator { WasTargetTyped: true } or
45764576
BoundConvertedSwitchExpression { WasTargetTyped: true } or
@@ -5817,6 +5817,14 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver)
58175817
{
58185818
resultType = null;
58195819
}
5820+
else if (IsTargetTypedExpression(consequence))
5821+
{
5822+
resultType = alternativeRValue.Type;
5823+
}
5824+
else if (IsTargetTypedExpression(alternative))
5825+
{
5826+
resultType = consequenceRValue.Type;
5827+
}
58205828
else
58215829
{
58225830
// Determine nested nullability using BestTypeInferrer.
@@ -9305,14 +9313,17 @@ private void TrackAnalyzedNullabilityThroughConversionGroup(TypeWithState result
93059313
while (conversionOpt != null && conversionOpt != convertedNode)
93069314
{
93079315
Debug.Assert(conversionOpt.ConversionGroupOpt == conversionGroup);
9308-
visitResult = withType(visitResult, conversionOpt.Type);
9316+
9317+
// https://github.com/dotnet/roslyn/issues/35046
9318+
// SetAnalyzedNullability will drop the type if the visitResult.RValueType.Type differs from conversionOpt.Type.
9319+
// (It will use the top-level nullability from visitResult, though.)
9320+
//
9321+
// Here, the visitResult represents the result of visiting the operand.
9322+
// Ideally, we would use the visitResult to reinfer the types of the containing conversions, and store those results here.
93099323
SetAnalyzedNullability(conversionOpt, visitResult);
9324+
93109325
conversionOpt = conversionOpt.Operand as BoundConversion;
93119326
}
9312-
9313-
static VisitResult withType(VisitResult visitResult, TypeSymbol newType) =>
9314-
new VisitResult(TypeWithState.Create(newType, visitResult.RValueType.State),
9315-
TypeWithAnnotations.Create(newType, visitResult.LValueType.NullableAnnotation));
93169327
}
93179328

93189329
/// <summary>
@@ -11282,8 +11293,6 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress
1128211293
}
1128311294
}
1128411295

11285-
// https://github.com/dotnet/roslyn/issues/33344: this fails to produce an updated tuple type for a default expression
11286-
// (should produce nullable element types for those elements that are of reference types)
1128711296
SetResultType(node, TypeWithState.ForType(type));
1128811297
return result;
1128911298
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -898,8 +898,11 @@ private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferTyp
898898
resultTypes.Add(armType);
899899
Join(ref endState, ref this.State);
900900

901-
// Build placeholders for inference in order to preserve annotations.
902-
placeholderBuilder.Add(CreatePlaceholderIfNecessary(expression, armType.ToTypeWithAnnotations(compilation)));
901+
if (!IsTargetTypedExpression(expression))
902+
{
903+
// Build placeholders for inference in order to preserve annotations.
904+
placeholderBuilder.Add(CreatePlaceholderIfNecessary(expression, armType.ToTypeWithAnnotations(compilation)));
905+
}
903906
}
904907

905908
SetState(endState);

0 commit comments

Comments
 (0)