Skip to content

Commit d838fac

Browse files
authored
Collection expressions: emit Array.Empty<T>() for [] with target type T[] or IEnumerable<T> (dotnet#69355)
1 parent f7cb1f2 commit d838fac

File tree

4 files changed

+408
-115
lines changed

4 files changed

+408
-115
lines changed

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

+22-20
Original file line numberDiff line numberDiff line change
@@ -675,29 +675,32 @@ private BoundCollectionExpression BindCollectionInitializerCollectionExpression(
675675
}
676676

677677
var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true };
678-
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod);
679678
var builder = ArrayBuilder<BoundExpression>.GetInstance(node.Elements.Length);
680-
foreach (var element in node.Elements)
679+
if (node.Elements.Length > 0)
681680
{
682-
var result = element switch
681+
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod);
682+
foreach (var element in node.Elements)
683683
{
684-
BoundBadExpression => element,
685-
BoundCollectionExpressionSpreadElement spreadElement => BindCollectionInitializerSpreadElementAddMethod(
686-
(SpreadElementSyntax)spreadElement.Syntax,
687-
spreadElement,
684+
var result = element switch
685+
{
686+
BoundBadExpression => element,
687+
BoundCollectionExpressionSpreadElement spreadElement => BindCollectionInitializerSpreadElementAddMethod(
688+
(SpreadElementSyntax)spreadElement.Syntax,
689+
spreadElement,
690+
collectionInitializerAddMethodBinder,
691+
implicitReceiver,
692+
diagnostics),
693+
_ => BindCollectionInitializerElementAddMethod(
694+
(ExpressionSyntax)element.Syntax,
695+
ImmutableArray.Create(element),
696+
hasEnumerableInitializerType: true,
688697
collectionInitializerAddMethodBinder,
689-
implicitReceiver,
690-
diagnostics),
691-
_ => BindCollectionInitializerElementAddMethod(
692-
(ExpressionSyntax)element.Syntax,
693-
ImmutableArray.Create(element),
694-
hasEnumerableInitializerType: true,
695-
collectionInitializerAddMethodBinder,
696-
diagnostics,
697-
implicitReceiver),
698-
};
699-
result.WasCompilerGenerated = true;
700-
builder.Add(result);
698+
diagnostics,
699+
implicitReceiver),
700+
};
701+
result.WasCompilerGenerated = true;
702+
builder.Add(result);
703+
}
701704
}
702705
return new BoundCollectionExpression(
703706
syntax,
@@ -718,7 +721,6 @@ private BoundCollectionExpression BindListInterfaceCollectionExpression(
718721
TypeSymbol elementType,
719722
BindingDiagnosticBag diagnostics)
720723
{
721-
// https://github.com/dotnet/roslyn/issues/68785: Emit [] as Array.Empty<T>() rather than a List<T>.
722724
var result = BindCollectionInitializerCollectionExpression(
723725
node,
724726
CollectionExpressionTypeKind.ListInterface,

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs

+57-22
Original file line numberDiff line numberDiff line change
@@ -1469,38 +1469,73 @@ private BoundExpression BuildParamsArray(
14691469
// if it's available. However, we also disable the optimization if we're in an expression lambda, the
14701470
// point of which is just to represent the semantics of an operation, and we don't know that all consumers
14711471
// of expression lambdas will appropriately understand Array.Empty<T>().
1472-
// We disable it for pointer types as well, since they cannot be used as Type Arguments.
14731472
if (arrayArgs.Length == 0
14741473
&& !_inExpressionLambda
1475-
&& paramArrayType is ArrayTypeSymbol ats // could be false if there's a semantic error, e.g. the params parameter type isn't an array
1476-
&& !ats.ElementType.IsPointerOrFunctionPointer())
1474+
&& paramArrayType is ArrayTypeSymbol ats) // could be false if there's a semantic error, e.g. the params parameter type isn't an array
14771475
{
1478-
MethodSymbol? arrayEmpty = _compilation.GetWellKnownTypeMember(WellKnownMember.System_Array__Empty) as MethodSymbol;
1479-
if (arrayEmpty != null) // will be null if Array.Empty<T> doesn't exist in reference assemblies
1476+
BoundExpression? arrayEmpty = CreateArrayEmptyCallIfAvailable(syntax, ats.ElementType);
1477+
if (arrayEmpty is { })
14801478
{
1481-
_diagnostics.ReportUseSite(arrayEmpty, syntax);
1482-
// return an invocation of "Array.Empty<T>()"
1483-
arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(ats.ElementType));
1484-
return new BoundCall(
1485-
syntax,
1486-
null,
1487-
arrayEmpty,
1488-
ImmutableArray<BoundExpression>.Empty,
1489-
default(ImmutableArray<string>),
1490-
default(ImmutableArray<RefKind>),
1491-
isDelegateCall: false,
1492-
expanded: false,
1493-
invokedAsExtensionMethod: false,
1494-
argsToParamsOpt: default(ImmutableArray<int>),
1495-
defaultArguments: default(BitVector),
1496-
resultKind: LookupResultKind.Viable,
1497-
type: arrayEmpty.ReturnType);
1479+
return arrayEmpty;
14981480
}
14991481
}
15001482

15011483
return CreateParamArrayArgument(syntax, paramArrayType, arrayArgs, _compilation, this);
15021484
}
15031485

1486+
private BoundExpression CreateEmptyArray(SyntaxNode syntax, ArrayTypeSymbol arrayType)
1487+
{
1488+
BoundExpression? arrayEmpty = CreateArrayEmptyCallIfAvailable(syntax, arrayType.ElementType);
1489+
if (arrayEmpty is { })
1490+
{
1491+
return arrayEmpty;
1492+
}
1493+
// new T[0]
1494+
return new BoundArrayCreation(
1495+
syntax,
1496+
ImmutableArray.Create<BoundExpression>(
1497+
new BoundLiteral(
1498+
syntax,
1499+
ConstantValue.Create(0),
1500+
_compilation.GetSpecialType(SpecialType.System_Int32))),
1501+
initializerOpt: null,
1502+
arrayType)
1503+
{ WasCompilerGenerated = true };
1504+
}
1505+
1506+
private BoundExpression? CreateArrayEmptyCallIfAvailable(SyntaxNode syntax, TypeSymbol elementType)
1507+
{
1508+
if (elementType.IsPointerOrFunctionPointer())
1509+
{
1510+
// Pointer types cannot be used as type arguments.
1511+
return null;
1512+
}
1513+
1514+
MethodSymbol? arrayEmpty = _compilation.GetWellKnownTypeMember(WellKnownMember.System_Array__Empty) as MethodSymbol;
1515+
if (arrayEmpty is null) // will be null if Array.Empty<T> doesn't exist in reference assemblies
1516+
{
1517+
return null;
1518+
}
1519+
1520+
_diagnostics.ReportUseSite(arrayEmpty, syntax);
1521+
// return an invocation of "Array.Empty<T>()"
1522+
arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(elementType));
1523+
return new BoundCall(
1524+
syntax,
1525+
null,
1526+
arrayEmpty,
1527+
ImmutableArray<BoundExpression>.Empty,
1528+
default(ImmutableArray<string>),
1529+
default(ImmutableArray<RefKind>),
1530+
isDelegateCall: false,
1531+
expanded: false,
1532+
invokedAsExtensionMethod: false,
1533+
argsToParamsOpt: default(ImmutableArray<int>),
1534+
defaultArguments: default(BitVector),
1535+
resultKind: LookupResultKind.Viable,
1536+
type: arrayEmpty.ReturnType);
1537+
}
1538+
15041539
private static BoundExpression CreateParamArrayArgument(SyntaxNode syntax,
15051540
TypeSymbol paramArrayType,
15061541
ImmutableArray<BoundExpression> arrayArgs,

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs

+44-20
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,27 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr
8181
else
8282
{
8383
int arrayLength = elements.Length;
84-
// https://github.com/dotnet/roslyn/issues/68785: Emit [] as Array.Empty<T>() rather than a List<T>.
85-
var initialization = (arrayLength == 0)
86-
? null
87-
: new BoundArrayInitialization(
88-
syntax,
89-
isInferred: false,
90-
elements.SelectAsArray(e => VisitExpression(e)));
91-
array = new BoundArrayCreation(
92-
syntax,
93-
ImmutableArray.Create<BoundExpression>(
94-
new BoundLiteral(
84+
if (arrayLength == 0)
85+
{
86+
array = CreateEmptyArray(syntax, arrayType);
87+
}
88+
else
89+
{
90+
var initialization = new BoundArrayInitialization(
9591
syntax,
96-
ConstantValue.Create(arrayLength),
97-
_compilation.GetSpecialType(SpecialType.System_Int32))),
98-
initialization,
99-
arrayType)
100-
{ WasCompilerGenerated = true };
92+
isInferred: false,
93+
elements.SelectAsArray(e => VisitExpression(e)));
94+
array = new BoundArrayCreation(
95+
syntax,
96+
ImmutableArray.Create<BoundExpression>(
97+
new BoundLiteral(
98+
syntax,
99+
ConstantValue.Create(arrayLength),
100+
_compilation.GetSpecialType(SpecialType.System_Int32))),
101+
initialization,
102+
arrayType)
103+
{ WasCompilerGenerated = true };
104+
}
101105
}
102106

103107
if (spanConstructor is null)
@@ -156,11 +160,31 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl
156160
private BoundExpression VisitListInterfaceCollectionExpression(BoundCollectionExpression node)
157161
{
158162
Debug.Assert(!_inExpressionLambda);
159-
Debug.Assert(node.Type is { });
163+
Debug.Assert(node.Type is NamedTypeSymbol);
164+
165+
var collectionType = (NamedTypeSymbol)node.Type;
166+
BoundExpression arrayOrList;
167+
168+
// Use Array.Empty<T>() rather than List<T> for an empty collection expression when
169+
// the target type is IEnumerable<T>, IReadOnlyCollection<T>, or IReadOnlyList<T>.
170+
if (node.Elements.Length == 0 &&
171+
collectionType is
172+
{
173+
OriginalDefinition.SpecialType:
174+
SpecialType.System_Collections_Generic_IEnumerable_T or
175+
SpecialType.System_Collections_Generic_IReadOnlyCollection_T or
176+
SpecialType.System_Collections_Generic_IReadOnlyList_T,
177+
TypeArgumentsWithAnnotationsNoUseSiteDiagnostics: [var elementType]
178+
})
179+
{
180+
arrayOrList = CreateEmptyArray(node.Syntax, ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType));
181+
}
182+
else
183+
{
184+
arrayOrList = VisitCollectionInitializerCollectionExpression(node, collectionType);
185+
}
160186

161-
// https://github.com/dotnet/roslyn/issues/68785: Emit [] as Array.Empty<T>() rather than a List<T>.
162-
var list = VisitCollectionInitializerCollectionExpression(node, node.Type);
163-
return _factory.Convert(node.Type, list);
187+
return _factory.Convert(collectionType, arrayOrList);
164188
}
165189

166190
private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollectionExpression node)

0 commit comments

Comments
 (0)