diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 97e870a509fa8..4eb8f05be9181 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -3804,6 +3804,11 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres var switchExpr = (BoundSwitchExpression)expr; return GetValEscape(switchExpr.SwitchArms.SelectAsArray(a => a.Value), scopeOfTheContainingExpression); + case BoundKind.ArrayOrSpanCollectionLiteralExpression: + case BoundKind.CollectionInitializerCollectionLiteralExpression: + // PROTOTYPE: Revisit if spans may be optimized to avoid heap allocation. + return CallingMethodScope; + default: // in error situations some unexpected nodes could make here // returning "scopeOfTheContainingExpression" seems safer than throwing. @@ -4282,6 +4287,11 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF return true; + case BoundKind.ArrayOrSpanCollectionLiteralExpression: + case BoundKind.CollectionInitializerCollectionLiteralExpression: + // PROTOTYPE: Revisit if spans may be optimized to avoid heap allocation. + return true; + default: // in error situations some unexpected nodes could make here // returning "false" seems safer than throwing. diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 6ff334f1c130c..ad1825936f54f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -230,6 +230,18 @@ BoundExpression createConversion( return ConvertObjectCreationExpression(syntax, (BoundUnconvertedObjectCreationExpression)source, conversion, isCast, destination, conversionGroupOpt, wasCompilerGenerated, diagnostics); } + if (conversion.IsCollectionLiteral) + { + return ConvertCollectionLiteralExpression( + (BoundUnconvertedCollectionLiteralExpression)source, + conversion, + isCast, + destination, + conversionGroupOpt, + wasCompilerGenerated, + diagnostics); + } + if (source.Kind == BoundKind.UnconvertedConditionalOperator) { Debug.Assert(source.Type is null); @@ -404,6 +416,187 @@ static BoundExpression bindObjectCreationExpression( } } + private BoundExpression ConvertCollectionLiteralExpression( + BoundUnconvertedCollectionLiteralExpression node, + Conversion conversion, + bool isCast, + TypeSymbol targetType, + ConversionGroup? conversionGroupOpt, + bool wasCompilerGenerated, + BindingDiagnosticBag diagnostics) + { + var syntax = (CSharpSyntaxNode)node.Syntax; + var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true }; + TypeSymbol? elementType; + BoundCollectionLiteralExpression collectionLiteral; + + if (targetType is ArrayTypeSymbol arrayType) + { + if (!arrayType.IsSZArray) + { + Error(diagnostics, ErrorCode.ERR_CollectionLiteralTargetTypeNotConstructible, syntax, targetType); + } + collectionLiteral = bindArrayOrSpan(syntax, spanConstructor: null, implicitReceiver, node.Initializers, arrayType.ElementType, diagnostics); + } + else if (isSpanType(targetType, WellKnownType.System_Span_T, out elementType)) + { + var spanConstructor = getSpanConstructor(syntax, targetType, WellKnownMember.System_Span_T__ctor_Array, diagnostics); + collectionLiteral = bindArrayOrSpan(syntax, spanConstructor, implicitReceiver, node.Initializers, elementType, diagnostics); + } + else if (isSpanType(targetType, WellKnownType.System_ReadOnlySpan_T, out elementType)) + { + var spanConstructor = getSpanConstructor(syntax, targetType, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, diagnostics); + collectionLiteral = bindArrayOrSpan(syntax, spanConstructor, implicitReceiver, node.Initializers, elementType, diagnostics); + } + else + { + collectionLiteral = bindCollectionInitializer(syntax, implicitReceiver, node.Initializers, diagnostics); + } + + return new BoundConversion( + syntax, + collectionLiteral, + conversion, + node.Binder.CheckOverflowAtRuntime, + explicitCastInCode: isCast && !wasCompilerGenerated, + conversionGroupOpt, + collectionLiteral.ConstantValueOpt, + targetType); + + bool isSpanType(TypeSymbol targetType, WellKnownType spanType, [NotNullWhen(true)] out TypeSymbol? elementType) + { + if (targetType is NamedTypeSymbol { Arity: 1 } namedType + && TypeSymbol.Equals(namedType.OriginalDefinition, Compilation.GetWellKnownType(spanType), TypeCompareKind.AllIgnoreOptions)) + { + elementType = namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + return true; + } + elementType = null; + return false; + } + + BoundArrayOrSpanCollectionLiteralExpression bindArrayOrSpan( + CSharpSyntaxNode syntax, + MethodSymbol? spanConstructor, + BoundObjectOrCollectionValuePlaceholder implicitReceiver, + ImmutableArray elements, + TypeSymbol elementType, + BindingDiagnosticBag diagnostics) + { + var builder = ArrayBuilder.GetInstance(elements.Length); + foreach (var element in elements) + { + builder.Add(convertArrayElement(element, elementType, diagnostics)); + } + return new BoundArrayOrSpanCollectionLiteralExpression( + syntax, + spanConstructor, + naturalTypeOpt: null, + wasTargetTyped: true, + implicitReceiver, + builder.ToImmutableAndFree(), + targetType) + { WasCompilerGenerated = wasCompilerGenerated }; + } + + BoundCollectionInitializerCollectionLiteralExpression bindCollectionInitializer( + CSharpSyntaxNode syntax, + BoundObjectOrCollectionValuePlaceholder implicitReceiver, + ImmutableArray elements, + BindingDiagnosticBag diagnostics) + { + BoundExpression collectionCreation; + if (targetType is NamedTypeSymbol namedType) + { + var analyzedArguments = AnalyzedArguments.GetInstance(); + collectionCreation = BindClassCreationExpression(syntax, namedType.Name, syntax, namedType, analyzedArguments, diagnostics); + collectionCreation.WasCompilerGenerated = true; + analyzedArguments.Free(); + } + else if (targetType is TypeParameterSymbol typeParameter) + { + var arguments = AnalyzedArguments.GetInstance(); + collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, arguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics); + arguments.Free(); + } + else + { + collectionCreation = new BoundBadExpression(syntax, LookupResultKind.NotCreatable, ImmutableArray.Empty, ImmutableArray.Empty, targetType); + } + + bool hasEnumerableInitializerType = collectionTypeImplementsIEnumerable(targetType, syntax, diagnostics); + if (!hasEnumerableInitializerType && !targetType.IsErrorType()) + { + Error(diagnostics, ErrorCode.ERR_CollectionLiteralTargetTypeNotConstructible, syntax, targetType); + } + + var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod); + var builder = ArrayBuilder.GetInstance(node.Initializers.Length); + foreach (var element in node.Initializers) + { + var result = BindCollectionInitializerElementAddMethod( + (ExpressionSyntax)element.Syntax, + ImmutableArray.Create(element), + hasEnumerableInitializerType, + collectionInitializerAddMethodBinder, + diagnostics, + implicitReceiver); + result.WasCompilerGenerated = true; + builder.Add(result); + } + return new BoundCollectionInitializerCollectionLiteralExpression( + syntax, + collectionCreation, + naturalTypeOpt: null, // PROTOTYPE: Support natural type. + wasTargetTyped: true, // PROTOTYPE: Support natural type. + implicitReceiver, + builder.ToImmutableAndFree(), + targetType) + { WasCompilerGenerated = wasCompilerGenerated }; + } + + MethodSymbol? getSpanConstructor(CSharpSyntaxNode syntax, TypeSymbol spanType, WellKnownMember spanMember, BindingDiagnosticBag diagnostics) + { + return (MethodSymbol?)GetWellKnownTypeMember(spanMember, diagnostics, syntax: syntax)?.SymbolAsMember((NamedTypeSymbol)spanType); + } + + bool collectionTypeImplementsIEnumerable(TypeSymbol targetType, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics) + { + // This implementation differs from CollectionInitializerTypeImplementsIEnumerable(). + // That method checks for an implicit conversion from IEnumerable to the collection type, + // but that would allow: Nullable s = []; + var ienumerableType = GetSpecialType(SpecialType.System_Collections_IEnumerable, diagnostics, syntax); + var allInterfaces = targetType is TypeParameterSymbol typeParameter + ? typeParameter.AllEffectiveInterfacesNoUseSiteDiagnostics + : targetType.AllInterfacesNoUseSiteDiagnostics; + return allInterfaces.Any(static (a, b) => a.Equals(b, TypeCompareKind.AllIgnoreOptions), ienumerableType); + } + + BoundExpression convertArrayElement(BoundExpression element, TypeSymbol elementType, BindingDiagnosticBag diagnostics) + { + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + var conversion = Conversions.ClassifyImplicitConversionFromExpression(element, elementType, ref useSiteInfo); + diagnostics.Add(element.Syntax, useSiteInfo); + bool hasErrors = !conversion.IsValid; + if (hasErrors) + { + GenerateImplicitConversionError(diagnostics, element.Syntax, conversion, element, elementType); + } + var result = CreateConversion( + element.Syntax, + element, + conversion, + isCast: false, + conversionGroupOpt: null, + wasCompilerGenerated: true, + destination: elementType, + diagnostics: diagnostics, + hasErrors: hasErrors); + result.WasCompilerGenerated = true; + return result; + } + } + /// /// Rewrite the subexpressions in a conditional expression to convert the whole thing to the destination type. /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index dd32d6393c35c..64222d1f7db87 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -755,8 +755,7 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, BindingDia return BadExpression(node); case SyntaxKind.CollectionCreationExpression: - // PROTOTYPE: Implement binding for this. - return BadExpression(node); + return BindCollectionLiteralExpression((CollectionCreationExpressionSyntax)node, diagnostics); case SyntaxKind.NullableType: // Not reachable during method body binding, but @@ -4482,6 +4481,41 @@ BoundExpression bindObjectCreationExpression(ObjectCreationExpressionSyntax node } } + private BoundExpression BindCollectionLiteralExpression(CollectionCreationExpressionSyntax syntax, BindingDiagnosticBag diagnostics) + { + MessageID.IDS_FeatureCollectionLiterals.CheckFeatureAvailability(diagnostics, syntax, syntax.OpenBracketToken.GetLocation()); + + var builder = ArrayBuilder.GetInstance(syntax.Elements.Count); + foreach (var element in syntax.Elements) + { + builder.Add(bindElement(element, diagnostics)); + } + return new BoundUnconvertedCollectionLiteralExpression(syntax, builder.ToImmutableAndFree(), this); + + BoundExpression bindElement(CollectionElementSyntax syntax, BindingDiagnosticBag diagnostics) + { + switch (syntax) + { + case ExpressionElementSyntax expressionElementSyntax: + return BindValue(expressionElementSyntax.Expression, diagnostics, BindValueKind.RValue); + + case DictionaryElementSyntax dictionaryElementSyntax: + _ = BindValue(dictionaryElementSyntax.KeyExpression, diagnostics, BindValueKind.RValue); + _ = BindValue(dictionaryElementSyntax.ValueExpression, diagnostics, BindValueKind.RValue); + Error(diagnostics, ErrorCode.ERR_CollectionLiteralElementNotImplemented, syntax); + return BadExpression(syntax); + + case SpreadElementSyntax spreadElementSyntax: + _ = BindValue(spreadElementSyntax.Expression, diagnostics, BindValueKind.RValue); + Error(diagnostics, ErrorCode.ERR_CollectionLiteralElementNotImplemented, syntax); + return BadExpression(syntax); + + default: + throw ExceptionUtilities.UnexpectedValue(syntax.Kind()); + } + } + } + private BoundExpression BindDelegateCreationExpression(ObjectCreationExpressionSyntax node, NamedTypeSymbol type, BindingDiagnosticBag diagnostics) { AnalyzedArguments analyzedArguments = AnalyzedArguments.GetInstance(); @@ -4756,7 +4790,6 @@ private BoundExpression MakeBadExpressionForObjectCreation(SyntaxNode node, Type return new BoundBadExpression(node, LookupResultKind.NotCreatable, ImmutableArray.Create(type), children.ToImmutableAndFree(), type) { WasCompilerGenerated = wasCompilerGenerated }; } -#nullable disable private BoundObjectInitializerExpressionBase BindInitializerExpression( InitializerExpressionSyntax syntax, @@ -4788,6 +4821,7 @@ private BoundObjectInitializerExpressionBase BindInitializerExpression( throw ExceptionUtilities.Unreachable(); } } +#nullable disable private BoundExpression BindInitializerExpressionOrValue( ExpressionSyntax syntax, @@ -5971,8 +6005,9 @@ private BoundExpression BindTypeParameterCreationExpression(ObjectCreationExpres return result; } +#nullable enable private BoundExpression BindTypeParameterCreationExpression( - SyntaxNode node, TypeParameterSymbol typeParameter, AnalyzedArguments analyzedArguments, InitializerExpressionSyntax initializerOpt, + SyntaxNode node, TypeParameterSymbol typeParameter, AnalyzedArguments analyzedArguments, InitializerExpressionSyntax? initializerOpt, SyntaxNode typeSyntax, bool wasTargetTyped, BindingDiagnosticBag diagnostics) { if (!typeParameter.HasConstructorConstraint && !typeParameter.IsValueType) @@ -5998,6 +6033,7 @@ private BoundExpression BindTypeParameterCreationExpression( return MakeBadExpressionForObjectCreation(node, typeParameter, analyzedArguments, initializerOpt, typeSyntax, diagnostics); } +#nullable disable /// /// Given the type containing constructors, gets the list of candidate instance constructors and uses overload resolution to determine which one should be called. diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index ff2fe9c168f7d..9261886f7c650 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -231,6 +231,7 @@ internal static Conversion GetTrivialConversion(ConversionKind kind) internal static Conversion ImplicitEnumeration => new Conversion(ConversionKind.ImplicitEnumeration); internal static Conversion ImplicitThrow => new Conversion(ConversionKind.ImplicitThrow); internal static Conversion ObjectCreation => new Conversion(ConversionKind.ObjectCreation); + internal static Conversion CollectionLiteral => new Conversion(ConversionKind.CollectionLiteral); internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction); internal static Conversion Boxing => new Conversion(ConversionKind.Boxing); internal static Conversion NullLiteral => new Conversion(ConversionKind.NullLiteral); @@ -631,6 +632,11 @@ public bool IsObjectCreation } } + /// + /// Returns true if the conversion is an implicit collection literal expression conversion. + /// + public bool IsCollectionLiteral => Kind == ConversionKind.CollectionLiteral; + /// /// Returns true if the conversion is an implicit switch expression conversion. /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs index 7656918721763..499878083c7a5 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs @@ -63,6 +63,7 @@ internal enum ConversionKind : byte DefaultLiteral, // a conversion from a `default` literal to any type ObjectCreation, // a conversion from a `new()` expression to any type + CollectionLiteral, // a conversion from a collection literal to any type InterpolatedStringHandler, // A conversion from an interpolated string literal to a type attributed with InterpolatedStringBuilderAttribute } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs index 08ebed29d4a48..ed3e5f029b635 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs @@ -51,6 +51,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind) case StackAllocToSpanType: case ImplicitPointer: case ObjectCreation: + case CollectionLiteral: return true; case ExplicitNumeric: diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index ea9a3976650c8..1eaff9c66d5ac 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -571,6 +571,7 @@ public Conversion ClassifyStandardConversion(BoundExpression sourceExpression, T return Conversion.NoConversion; } + // PROTOTYPE: Ensure collection literal conversions are not considered standard implicit conversions. private static bool IsStandardImplicitConversionFromExpression(ConversionKind kind) { if (IsStandardImplicitConversionFromType(kind)) @@ -1094,6 +1095,9 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi case BoundKind.UnconvertedObjectCreationExpression: return Conversion.ObjectCreation; + + case BoundKind.UnconvertedCollectionLiteralExpression: + return Conversion.CollectionLiteral; } return Conversion.NoConversion; diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index db0a64e70a2d5..523c2fcc16961 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1866,6 +1866,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +