Skip to content

Commit

Permalink
Initialization of nullable collections
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Sep 7, 2023
1 parent d8b5411 commit e92a30f
Show file tree
Hide file tree
Showing 6 changed files with 495 additions and 49 deletions.
12 changes: 11 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ BoundExpression createConversion(

if (source.Kind == BoundKind.UnconvertedCollectionExpression)
{
Debug.Assert(conversion.IsCollectionExpression || !conversion.Exists);
Debug.Assert(conversion.IsCollectionExpression
|| (conversion.IsNullable && conversion.UnderlyingConversions[0].IsCollectionExpression)
|| !conversion.Exists);

var collectionExpression = ConvertCollectionExpression(
(BoundUnconvertedCollectionExpression)source,
Expand Down Expand Up @@ -541,6 +543,14 @@ private BoundExpression ConvertCollectionExpression(
Conversion conversion,
BindingDiagnosticBag diagnostics)
{
if (targetType.IsNullableType())
{
Debug.Assert(conversion.IsNullable);
targetType = targetType.GetNullableUnderlyingType();
conversion = conversion.UnderlyingConversions[0];
_ = GetSpecialTypeMember(SpecialMember.System_Nullable_T__ctor, diagnostics, syntax: node.Syntax);
}

var collectionTypeKind = conversion.GetCollectionExpressionTypeKind(out var elementType);

if (collectionTypeKind == CollectionExpressionTypeKind.None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi
return Conversion.ObjectCreation;

case BoundKind.UnconvertedCollectionExpression:
var collectionExpressionConversion = GetCollectionExpressionConversion((BoundUnconvertedCollectionExpression)sourceExpression, destination, ref useSiteInfo);
var collectionExpressionConversion = GetImplicitCollectionExpressionConversion((BoundUnconvertedCollectionExpression)sourceExpression, destination, useSiteInfo);
if (collectionExpressionConversion.Exists)
{
return collectionExpressionConversion;
Expand All @@ -1129,6 +1129,45 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi
return Conversion.NoConversion;
}

#nullable enable
private Conversion GetImplicitCollectionExpressionConversion(BoundUnconvertedCollectionExpression collectionExpression, TypeSymbol destination, CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var collectionExpressionConversion = GetCollectionExpressionConversion(collectionExpression, destination, ref useSiteInfo);
if (collectionExpressionConversion.Exists)
{
return collectionExpressionConversion;
}

// strip nullable from the destination
//
// the following should work and it is an ImplicitNullable conversion
// ImmutableArray<int>? x = [1, 2];
if (IsSystemNullable(destination, out var underlyingDestination))
{
var underlyingConversion = GetCollectionExpressionConversion(collectionExpression, underlyingDestination, ref useSiteInfo);
if (underlyingConversion.Exists)
{
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(underlyingConversion));
}
}

return Conversion.NoConversion;
}

private static bool IsSystemNullable(TypeSymbol type, [NotNullWhen(true)] out TypeSymbol? underlyingType)
{
if (type is NamedTypeSymbol nt
&& nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T)
{
underlyingType = nt.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
return true;
}

underlyingType = null;
return false;
}
#nullable disable

private Conversion GetSwitchExpressionConversion(BoundExpression source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Compilation is not null);
Expand Down Expand Up @@ -1228,9 +1267,8 @@ private static Conversion ClassifyImplicitConstantExpressionConversion(BoundExpr
// int? x = 1;
if (destination.Kind == SymbolKind.NamedType)
{
var nt = (NamedTypeSymbol)destination;
if (nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T &&
HasImplicitConstantExpressionConversion(source, nt.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type))
if (IsSystemNullable(destination, out var underlyingDestination) &&
HasImplicitConstantExpressionConversion(source, underlyingDestination))
{
return Conversion.ImplicitNullableWithImplicitConstantUnderlying;
}
Expand All @@ -1253,17 +1291,12 @@ private Conversion ClassifyImplicitTupleLiteralConversion(BoundTupleLiteral sour
//
// the following should work and it is an ImplicitNullable conversion
// (int, double)? x = (1,2);
if (destination.Kind == SymbolKind.NamedType)
if (IsSystemNullable(destination, out var underlyingDestination))
{
var nt = (NamedTypeSymbol)destination;
if (nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T)
var underlyingTupleConversion = GetImplicitTupleLiteralConversion(source, underlyingDestination, ref useSiteInfo);
if (underlyingTupleConversion.Exists)
{
var underlyingTupleConversion = GetImplicitTupleLiteralConversion(source, nt.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type, ref useSiteInfo);

if (underlyingTupleConversion.Exists)
{
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(underlyingTupleConversion));
}
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(underlyingTupleConversion));
}
}

Expand All @@ -1286,10 +1319,9 @@ private Conversion ClassifyExplicitTupleLiteralConversion(BoundTupleLiteral sour
// var x = ((byte, string)?)(1,null);
if (destination.Kind == SymbolKind.NamedType)
{
var nt = (NamedTypeSymbol)destination;
if (nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T)
if (IsSystemNullable(destination, out var underlyingDestination))
{
var underlyingTupleConversion = GetExplicitTupleLiteralConversion(source, nt.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type, isChecked: isChecked, ref useSiteInfo, forCast);
var underlyingTupleConversion = GetExplicitTupleLiteralConversion(source, underlyingDestination, isChecked: isChecked, ref useSiteInfo, forCast);

if (underlyingTupleConversion.Exists)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,8 @@ private void MakeCollectionExpressionTypeInferences(
ExactOrBoundsKind kind,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (target.Type is null)
TypeSymbol targetType = target.Type;
if (targetType is null)
{
return;
}
Expand All @@ -649,7 +650,8 @@ private void MakeCollectionExpressionTypeInferences(
return;
}

if (!binder.TryGetCollectionIterationType((ExpressionSyntax)argument.Syntax, target.Type, out TypeWithAnnotations targetElementType))
var underlyingTargetType = targetType.IsNullableType() ? targetType.GetNullableUnderlyingType() : targetType;
if (!binder.TryGetCollectionIterationType((ExpressionSyntax)argument.Syntax, underlyingTargetType, out TypeWithAnnotations targetElementType))
{
return;
}
Expand Down
16 changes: 15 additions & 1 deletion src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2192,10 +2192,24 @@ internal CSharpTypeInfo GetTypeInfoForNode(
convertedNullability = convertedCollection.TopLevelNullability;
conversion = convertedCollectionConversion;
}
else if (highestBoundExpr is BoundConversion { ConversionKind: ConversionKind.ImplicitNullable, Conversion.UnderlyingConversions: [{ Kind: ConversionKind.CollectionExpression }] } boundConversion)
{
convertedType = highestBoundExpr.Type;
convertedNullability = convertedCollection.TopLevelNullability;
conversion = boundConversion.Conversion;
}
else if (boundNodeForSyntacticParent is BoundConversion parentConversion)
{
// There was an explicit cast on top of the collection expression.
convertedType = parentConversion.Type;
convertedNullability = nullability;
conversion = parentConversion.Conversion;
}
else
{
// There was an explicit cast on top of this.
// Error scenario like `object x = [];`
(convertedType, convertedNullability) = (convertedCollection.Type, nullability);

conversion = convertedCollection.CollectionTypeKind == CollectionExpressionTypeKind.None
? Conversion.NoConversion
: Conversion.CollectionExpression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public override BoundNode VisitConversion(BoundConversion node)

return objectCreation;

case ConversionKind.ImplicitNullable when node.Conversion.UnderlyingConversions[0].Kind is ConversionKind.CollectionExpression:
var rewrittenCollection = RewriteCollectionExpressionConversion(node.Conversion.UnderlyingConversions[0], (BoundCollectionExpression)node.Operand);
return ConvertToNullable(node.Syntax, node.Type, rewrittenCollection);

case ConversionKind.CollectionExpression:
return RewriteCollectionExpressionConversion(node.Conversion, (BoundCollectionExpression)node.Operand);
}
Expand Down
Loading

0 comments on commit e92a30f

Please sign in to comment.