-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Collection expressions: require applicable constructor and Add method for target type implementing IEnumerable #71592
Changes from all commits
ec6908b
1ad210c
b6491c6
6d4f78b
008a562
3dabaf1
0550695
5a57f8c
463737e
3f424df
559c4fc
59338d8
86eb437
a59cfe2
548d410
46bd28d
4faea26
7bb2741
2168070
e15911c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -654,35 +654,25 @@ private BoundCollectionExpression ConvertCollectionExpression( | |
if (collectionTypeKind is CollectionExpressionTypeKind.ImplementsIEnumerable) | ||
{ | ||
implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true }; | ||
if (targetType is NamedTypeSymbol namedType) | ||
{ | ||
var analyzedArguments = AnalyzedArguments.GetInstance(); | ||
// https://github.com/dotnet/roslyn/issues/68785: Use ctor with `int capacity` when the size is known. | ||
collectionCreation = BindClassCreationExpression(syntax, namedType.Name, syntax, namedType, analyzedArguments, diagnostics); | ||
collectionCreation.WasCompilerGenerated = true; | ||
analyzedArguments.Free(); | ||
} | ||
else if (targetType is TypeParameterSymbol typeParameter) | ||
{ | ||
var analyzedArguments = AnalyzedArguments.GetInstance(); | ||
collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, analyzedArguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics); | ||
collectionCreation.WasCompilerGenerated = true; | ||
analyzedArguments.Free(); | ||
} | ||
else | ||
{ | ||
collectionCreation = new BoundBadExpression(syntax, LookupResultKind.NotCreatable, ImmutableArray<Symbol?>.Empty, ImmutableArray<BoundExpression>.Empty, targetType); | ||
} | ||
collectionCreation = BindCollectionExpressionConstructor(syntax, targetType, diagnostics); | ||
|
||
var collectionInitializerAddMethodBinder = this.WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod); | ||
foreach (var element in elements) | ||
{ | ||
BoundNode convertedElement = BindCollectionExpressionElementAddMethod( | ||
element, | ||
collectionInitializerAddMethodBinder, | ||
implicitReceiver, | ||
diagnostics, | ||
out _); | ||
BoundNode convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ? | ||
(BoundNode)BindCollectionExpressionSpreadElementAddMethod( | ||
(SpreadElementSyntax)spreadElement.Syntax, | ||
spreadElement, | ||
collectionInitializerAddMethodBinder, | ||
implicitReceiver, | ||
diagnostics) : | ||
BindCollectionInitializerElementAddMethod( | ||
(ExpressionSyntax)element.Syntax, | ||
ImmutableArray.Create((BoundExpression)element), | ||
hasEnumerableInitializerType: true, | ||
collectionInitializerAddMethodBinder, | ||
diagnostics, | ||
implicitReceiver); | ||
builder.Add(convertedElement); | ||
} | ||
} | ||
|
@@ -766,6 +756,50 @@ BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, Type | |
} | ||
} | ||
|
||
internal BoundExpression BindCollectionExpressionConstructor(SyntaxNode syntax, TypeSymbol targetType, BindingDiagnosticBag diagnostics) | ||
{ | ||
BoundExpression collectionCreation; | ||
var analyzedArguments = AnalyzedArguments.GetInstance(); | ||
if (targetType is NamedTypeSymbol namedType) | ||
{ | ||
var binder = WithAdditionalFlags(BinderFlags.CollectionExpressionConversionValidation); | ||
collectionCreation = binder.BindClassCreationExpression(syntax, namedType.Name, syntax, namedType, analyzedArguments, diagnostics); | ||
collectionCreation.WasCompilerGenerated = true; | ||
} | ||
else if (targetType is TypeParameterSymbol typeParameter) | ||
{ | ||
collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, analyzedArguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics); | ||
collectionCreation.WasCompilerGenerated = true; | ||
} | ||
else | ||
{ | ||
throw ExceptionUtilities.UnexpectedValue(targetType); | ||
} | ||
analyzedArguments.Free(); | ||
return collectionCreation; | ||
} | ||
|
||
internal bool HasCollectionExpressionApplicableConstructor(SyntaxNode syntax, TypeSymbol targetType, BindingDiagnosticBag diagnostics) | ||
{ | ||
var collectionCreation = BindCollectionExpressionConstructor(syntax, targetType, diagnostics); | ||
return !collectionCreation.HasErrors; | ||
} | ||
|
||
internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, TypeSymbol targetType, TypeSymbol elementType, BindingDiagnosticBag diagnostics) | ||
{ | ||
var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true }; | ||
var elementPlaceholder = new BoundValuePlaceholder(syntax, elementType) { WasCompilerGenerated = true }; | ||
var addMethodBinder = WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod | BinderFlags.CollectionExpressionConversionValidation); | ||
var result = BindCollectionInitializerElementAddMethod( | ||
syntax, | ||
ImmutableArray.Create<BoundExpression>(elementPlaceholder), | ||
hasEnumerableInitializerType: true, | ||
addMethodBinder, | ||
diagnostics, | ||
implicitReceiver); | ||
return !result.HasErrors; | ||
} | ||
|
||
/// <summary> | ||
/// If the element is from a collection type where elements are added with collection initializers, | ||
/// return the argument to the collection initializer Add method or null if the element is not a | ||
|
@@ -865,10 +899,24 @@ private void GenerateImplicitConversionErrorForCollectionExpression( | |
|
||
if (collectionTypeKind != CollectionExpressionTypeKind.None) | ||
{ | ||
var elements = node.Elements; | ||
var elementType = elementTypeWithAnnotations.Type; | ||
Debug.Assert(elementType is { }); | ||
|
||
var elements = node.Elements; | ||
if (collectionTypeKind == CollectionExpressionTypeKind.ImplementsIEnumerable) | ||
{ | ||
if (!HasCollectionExpressionApplicableConstructor(node.Syntax, targetType, diagnostics)) | ||
{ | ||
reportedErrors = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the other hand, if "error CS1729: '{targetType}' does not contain a constructor that takes 0 arguments" and the like are the only errors that we can get from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there are several tests that hit this with a type parameter, including |
||
} | ||
|
||
if (elements.Length > 0 && | ||
!HasCollectionExpressionApplicableAddMethod(node.Syntax, targetType, elementType, diagnostics)) | ||
{ | ||
reportedErrors = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like we end up here for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dependencies can be dropped too, since we are reporting an error. Accurate tracking of dependencies is not supported for error scenarios There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can report "error CS1061: '{targetType}' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)" when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is quite possible that we only need to worry when |
||
} | ||
} | ||
|
||
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
foreach (var element in elements) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,6 @@ | |
using System.Linq; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp | ||
{ | ||
|
@@ -182,8 +181,22 @@ protected override Conversion GetCollectionExpressionConversion( | |
} | ||
|
||
Debug.Assert(elementType is { }); | ||
|
||
var elements = node.Elements; | ||
|
||
if (collectionTypeKind == CollectionExpressionTypeKind.ImplementsIEnumerable) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to update breaking change document with the additional conversion requirements: an accessible .ctor callable with no arguments, and an accessible Add method callable with an argument of the iteration type. For instance, |
||
{ | ||
if (!_binder.HasCollectionExpressionApplicableConstructor(syntax, targetType, BindingDiagnosticBag.Discarded)) | ||
{ | ||
return Conversion.NoConversion; | ||
} | ||
|
||
if (elements.Length > 0 && | ||
!_binder.HasCollectionExpressionApplicableAddMethod(syntax, targetType, elementType, BindingDiagnosticBag.Discarded)) | ||
{ | ||
return Conversion.NoConversion; | ||
} | ||
} | ||
|
||
var builder = ArrayBuilder<Conversion>.GetInstance(elements.Length); | ||
foreach (var element in elements) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we assuming a general error "No conversion" has been reported elsewhere? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, consider adding a comment pointing to the code doing that