-
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 15 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 | ||
|
@@ -868,6 +902,19 @@ private void GenerateImplicitConversionErrorForCollectionExpression( | |
var elementType = elementTypeWithAnnotations.Type; | ||
Debug.Assert(elementType is { }); | ||
|
||
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 (!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 elements = node.Elements; | ||
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 | ||
{ | ||
|
@@ -183,6 +182,19 @@ protected override Conversion GetCollectionExpressionConversion( | |
|
||
Debug.Assert(elementType is { }); | ||
|
||
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 (!_binder.HasCollectionExpressionApplicableAddMethod(syntax, targetType, elementType, BindingDiagnosticBag.Discarded)) | ||
{ | ||
return Conversion.NoConversion; | ||
} | ||
} | ||
|
||
var elements = node.Elements; | ||
var builder = ArrayBuilder<Conversion>.GetInstance(elements.Length); | ||
foreach (var element in elements) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -504,8 +504,25 @@ internal void ReportDiagnostics<T>( | |
// but no argument was supplied for it then the first such method is | ||
// the best bad method. | ||
case MemberResolutionKind.RequiredParameterMissing: | ||
// CONSIDER: for consistency with dev12, we would goto default except in omitted ref cases. | ||
ReportMissingRequiredParameter(firstSupported, diagnostics, delegateTypeBeingInvoked, symbols, location); | ||
if ((binder.Flags & BinderFlags.CollectionExpressionConversionValidation) != 0) | ||
{ | ||
if (receiver is null) | ||
{ | ||
Debug.Assert(firstSupported.Member is MethodSymbol { MethodKind: MethodKind.Constructor }); | ||
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingConstructor, location); | ||
} | ||
else | ||
{ | ||
Debug.Assert(firstSupported.Member is MethodSymbol { Name: "Add" }); | ||
int argumentOffset = arguments.IsExtensionMethodInvocation ? 1 : 0; | ||
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, arguments.Arguments[argumentOffset].Type, firstSupported.Member); | ||
} | ||
} | ||
else | ||
{ | ||
// CONSIDER: for consistency with dev12, we would goto default except in omitted ref cases. | ||
ReportMissingRequiredParameter(firstSupported, diagnostics, delegateTypeBeingInvoked, symbols, location); | ||
} | ||
return; | ||
|
||
// NOTE: For some reason, there is no specific handling for this result kind. | ||
|
@@ -1092,18 +1109,29 @@ private bool HadBadArguments( | |
// ErrorCode.ERR_BadArgTypesForCollectionAdd or ErrorCode.ERR_InitializerAddHasParamModifiers | ||
// as there is no explicit call to Add method. | ||
|
||
foreach (var parameter in method.GetParameters()) | ||
int argumentOffset = arguments.IsExtensionMethodInvocation ? 1 : 0; | ||
var parameters = method.GetParameters(); | ||
|
||
for (int i = argumentOffset; i < parameters.Length; i++) | ||
{ | ||
if (parameter.RefKind != RefKind.None) | ||
if (parameters[i].RefKind != RefKind.None) | ||
{ | ||
// The best overloaded method match '{0}' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters. | ||
diagnostics.Add(ErrorCode.ERR_InitializerAddHasParamModifiers, location, symbols, method); | ||
return true; | ||
} | ||
} | ||
|
||
// The best overloaded Add method '{0}' for the collection initializer has some invalid arguments | ||
diagnostics.Add(ErrorCode.ERR_BadArgTypesForCollectionAdd, location, symbols, method); | ||
if (flags.Includes(BinderFlags.CollectionExpressionConversionValidation)) | ||
{ | ||
Debug.Assert(arguments.Arguments.Count == argumentOffset + 1); | ||
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, arguments.Arguments[argumentOffset].Type, method); | ||
} | ||
else | ||
{ | ||
// The best overloaded Add method '{0}' for the collection initializer has some invalid arguments | ||
diagnostics.Add(ErrorCode.ERR_BadArgTypesForCollectionAdd, location, symbols, method); | ||
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. It looks like there is a test covering this code path. |
||
} | ||
} | ||
|
||
foreach (var arg in badArg.Result.BadArgumentsOpt.TrueBits()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6859,6 +6859,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="ERR_CollectionExpressionTargetNoElementType" xml:space="preserve"> | ||
<value>Collection expression target '{0}' has no element type.</value> | ||
</data> | ||
<data name="ERR_CollectionExpressionMissingConstructor" xml:space="preserve"> | ||
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. I found tests for missing ctor, but couldnt' find tests for inaccessible constructors. Do we have some? I wonder if this error message should mention the accessibility requirement. 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. Added some specific tests for inaccessible constructor and |
||
<value>Collection expression type must have an applicable constructor that can be called with no arguments.</value> | ||
</data> | ||
<data name="ERR_CollectionExpressionMissingAdd" xml:space="preserve"> | ||
<value>Collection expression type must have an applicable instance or extension method 'Add' that can be called with an argument of iteration type '{0}'. The best overloaded method is '{1}'.</value> | ||
</data> | ||
<data name="ERR_CollectionBuilderAttributeInvalidType" xml:space="preserve"> | ||
<value>The CollectionBuilderAttribute builder type must be a non-generic class or struct.</value> | ||
</data> | ||
|
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