Skip to content

Commit

Permalink
Collection expressions: emit Array.Empty<T>() for [] with target type…
Browse files Browse the repository at this point in the history
… T[] or IEnumerable<T> (#69355)
  • Loading branch information
cston authored Aug 9, 2023
1 parent f7cb1f2 commit d838fac
Show file tree
Hide file tree
Showing 4 changed files with 408 additions and 115 deletions.
42 changes: 22 additions & 20 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -675,29 +675,32 @@ private BoundCollectionExpression BindCollectionInitializerCollectionExpression(
}

var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true };
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod);
var builder = ArrayBuilder<BoundExpression>.GetInstance(node.Elements.Length);
foreach (var element in node.Elements)
if (node.Elements.Length > 0)
{
var result = element switch
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod);
foreach (var element in node.Elements)
{
BoundBadExpression => element,
BoundCollectionExpressionSpreadElement spreadElement => BindCollectionInitializerSpreadElementAddMethod(
(SpreadElementSyntax)spreadElement.Syntax,
spreadElement,
var result = element switch
{
BoundBadExpression => element,
BoundCollectionExpressionSpreadElement spreadElement => BindCollectionInitializerSpreadElementAddMethod(
(SpreadElementSyntax)spreadElement.Syntax,
spreadElement,
collectionInitializerAddMethodBinder,
implicitReceiver,
diagnostics),
_ => BindCollectionInitializerElementAddMethod(
(ExpressionSyntax)element.Syntax,
ImmutableArray.Create(element),
hasEnumerableInitializerType: true,
collectionInitializerAddMethodBinder,
implicitReceiver,
diagnostics),
_ => BindCollectionInitializerElementAddMethod(
(ExpressionSyntax)element.Syntax,
ImmutableArray.Create(element),
hasEnumerableInitializerType: true,
collectionInitializerAddMethodBinder,
diagnostics,
implicitReceiver),
};
result.WasCompilerGenerated = true;
builder.Add(result);
diagnostics,
implicitReceiver),
};
result.WasCompilerGenerated = true;
builder.Add(result);
}
}
return new BoundCollectionExpression(
syntax,
Expand All @@ -718,7 +721,6 @@ private BoundCollectionExpression BindListInterfaceCollectionExpression(
TypeSymbol elementType,
BindingDiagnosticBag diagnostics)
{
// https://github.com/dotnet/roslyn/issues/68785: Emit [] as Array.Empty<T>() rather than a List<T>.
var result = BindCollectionInitializerCollectionExpression(
node,
CollectionExpressionTypeKind.ListInterface,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1469,38 +1469,73 @@ private BoundExpression BuildParamsArray(
// if it's available. However, we also disable the optimization if we're in an expression lambda, the
// point of which is just to represent the semantics of an operation, and we don't know that all consumers
// of expression lambdas will appropriately understand Array.Empty<T>().
// We disable it for pointer types as well, since they cannot be used as Type Arguments.
if (arrayArgs.Length == 0
&& !_inExpressionLambda
&& paramArrayType is ArrayTypeSymbol ats // could be false if there's a semantic error, e.g. the params parameter type isn't an array
&& !ats.ElementType.IsPointerOrFunctionPointer())
&& paramArrayType is ArrayTypeSymbol ats) // could be false if there's a semantic error, e.g. the params parameter type isn't an array
{
MethodSymbol? arrayEmpty = _compilation.GetWellKnownTypeMember(WellKnownMember.System_Array__Empty) as MethodSymbol;
if (arrayEmpty != null) // will be null if Array.Empty<T> doesn't exist in reference assemblies
BoundExpression? arrayEmpty = CreateArrayEmptyCallIfAvailable(syntax, ats.ElementType);
if (arrayEmpty is { })
{
_diagnostics.ReportUseSite(arrayEmpty, syntax);
// return an invocation of "Array.Empty<T>()"
arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(ats.ElementType));
return new BoundCall(
syntax,
null,
arrayEmpty,
ImmutableArray<BoundExpression>.Empty,
default(ImmutableArray<string>),
default(ImmutableArray<RefKind>),
isDelegateCall: false,
expanded: false,
invokedAsExtensionMethod: false,
argsToParamsOpt: default(ImmutableArray<int>),
defaultArguments: default(BitVector),
resultKind: LookupResultKind.Viable,
type: arrayEmpty.ReturnType);
return arrayEmpty;
}
}

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

private BoundExpression CreateEmptyArray(SyntaxNode syntax, ArrayTypeSymbol arrayType)
{
BoundExpression? arrayEmpty = CreateArrayEmptyCallIfAvailable(syntax, arrayType.ElementType);
if (arrayEmpty is { })
{
return arrayEmpty;
}
// new T[0]
return new BoundArrayCreation(
syntax,
ImmutableArray.Create<BoundExpression>(
new BoundLiteral(
syntax,
ConstantValue.Create(0),
_compilation.GetSpecialType(SpecialType.System_Int32))),
initializerOpt: null,
arrayType)
{ WasCompilerGenerated = true };
}

private BoundExpression? CreateArrayEmptyCallIfAvailable(SyntaxNode syntax, TypeSymbol elementType)
{
if (elementType.IsPointerOrFunctionPointer())
{
// Pointer types cannot be used as type arguments.
return null;
}

MethodSymbol? arrayEmpty = _compilation.GetWellKnownTypeMember(WellKnownMember.System_Array__Empty) as MethodSymbol;
if (arrayEmpty is null) // will be null if Array.Empty<T> doesn't exist in reference assemblies
{
return null;
}

_diagnostics.ReportUseSite(arrayEmpty, syntax);
// return an invocation of "Array.Empty<T>()"
arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(elementType));
return new BoundCall(
syntax,
null,
arrayEmpty,
ImmutableArray<BoundExpression>.Empty,
default(ImmutableArray<string>),
default(ImmutableArray<RefKind>),
isDelegateCall: false,
expanded: false,
invokedAsExtensionMethod: false,
argsToParamsOpt: default(ImmutableArray<int>),
defaultArguments: default(BitVector),
resultKind: LookupResultKind.Viable,
type: arrayEmpty.ReturnType);
}

private static BoundExpression CreateParamArrayArgument(SyntaxNode syntax,
TypeSymbol paramArrayType,
ImmutableArray<BoundExpression> arrayArgs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,27 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr
else
{
int arrayLength = elements.Length;
// https://github.com/dotnet/roslyn/issues/68785: Emit [] as Array.Empty<T>() rather than a List<T>.
var initialization = (arrayLength == 0)
? null
: new BoundArrayInitialization(
syntax,
isInferred: false,
elements.SelectAsArray(e => VisitExpression(e)));
array = new BoundArrayCreation(
syntax,
ImmutableArray.Create<BoundExpression>(
new BoundLiteral(
if (arrayLength == 0)
{
array = CreateEmptyArray(syntax, arrayType);
}
else
{
var initialization = new BoundArrayInitialization(
syntax,
ConstantValue.Create(arrayLength),
_compilation.GetSpecialType(SpecialType.System_Int32))),
initialization,
arrayType)
{ WasCompilerGenerated = true };
isInferred: false,
elements.SelectAsArray(e => VisitExpression(e)));
array = new BoundArrayCreation(
syntax,
ImmutableArray.Create<BoundExpression>(
new BoundLiteral(
syntax,
ConstantValue.Create(arrayLength),
_compilation.GetSpecialType(SpecialType.System_Int32))),
initialization,
arrayType)
{ WasCompilerGenerated = true };
}
}

if (spanConstructor is null)
Expand Down Expand Up @@ -156,11 +160,31 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl
private BoundExpression VisitListInterfaceCollectionExpression(BoundCollectionExpression node)
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(node.Type is { });
Debug.Assert(node.Type is NamedTypeSymbol);

var collectionType = (NamedTypeSymbol)node.Type;
BoundExpression arrayOrList;

// Use Array.Empty<T>() rather than List<T> for an empty collection expression when
// the target type is IEnumerable<T>, IReadOnlyCollection<T>, or IReadOnlyList<T>.
if (node.Elements.Length == 0 &&
collectionType is
{
OriginalDefinition.SpecialType:
SpecialType.System_Collections_Generic_IEnumerable_T or
SpecialType.System_Collections_Generic_IReadOnlyCollection_T or
SpecialType.System_Collections_Generic_IReadOnlyList_T,
TypeArgumentsWithAnnotationsNoUseSiteDiagnostics: [var elementType]
})
{
arrayOrList = CreateEmptyArray(node.Syntax, ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType));
}
else
{
arrayOrList = VisitCollectionInitializerCollectionExpression(node, collectionType);
}

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

private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollectionExpression node)
Expand Down
Loading

0 comments on commit d838fac

Please sign in to comment.