-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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 Add method callable with a single argument for class and struct types that do not use [CollectionBuilder] #72654
Changes from 2 commits
442498d
88caf12
726896e
357dc53
aa38c7f
9d7e276
75978ab
9044807
387cb37
d8bbb8a
58744f2
52ab501
837dc73
d7811fc
59bfc0d
70f94fb
4330e53
69d499d
1316830
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 |
---|---|---|
|
@@ -1285,21 +1285,112 @@ static bool bindInvocationExpressionContinued( | |
} | ||
} | ||
|
||
internal bool HasCollectionExpressionAddMethod(SyntaxNode syntax, TypeSymbol targetType) | ||
{ | ||
const string methodName = "Add"; | ||
var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; | ||
|
||
var lookupResult = LookupResult.GetInstance(); | ||
LookupInstanceMember(lookupResult, targetType, leftIsBaseReference: false, rightName: methodName, rightArity: 0, invoked: true, ref useSiteInfo); | ||
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. Rather than trying to duplicate the lookup code, I think we should use |
||
bool anyApplicable = lookupResult.IsMultiViable && | ||
lookupResult.Symbols.Any(s => s is MethodSymbol m && isApplicableAddMethod(m, expectingExtensionMethod: false)); | ||
lookupResult.Free(); | ||
if (anyApplicable) | ||
{ | ||
return true; | ||
} | ||
|
||
var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = 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.
I assume the logic starting from here to the end of the method corresponds to what is happening in |
||
foreach (var scope in new ExtensionMethodScopes(this)) | ||
{ | ||
var methodGroup = MethodGroup.GetInstance(); | ||
PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, syntax, implicitReceiver, rightName: methodName, typeArgumentsWithAnnotations: default, BindingDiagnosticBag.Discarded); | ||
anyApplicable = methodGroup.Methods.Any(m => isApplicableAddMethod(m, expectingExtensionMethod: true)); | ||
methodGroup.Free(); | ||
if (anyApplicable) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
|
||
static bool isApplicableAddMethod(MethodSymbol method, bool expectingExtensionMethod) | ||
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. |
||
{ | ||
if (method.IsStatic != expectingExtensionMethod) | ||
{ | ||
return false; | ||
} | ||
|
||
var parameters = method.Parameters; | ||
int valueIndex = expectingExtensionMethod ? 1 : 0; | ||
int requiredLength = valueIndex + 1; | ||
if (parameters.Length < requiredLength) | ||
{ | ||
return false; | ||
} | ||
|
||
// Any trailing parameters must be optional or params. | ||
for (int i = requiredLength; i < parameters.Length; i++) | ||
{ | ||
if (parameters[i] is { IsOptional: false, IsParams: false }) | ||
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. The
|
||
{ | ||
return false; | ||
} | ||
} | ||
|
||
// Value parameter must be by value, in, or ref readonly. | ||
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. |
||
if (parameters[valueIndex].RefKind is not (RefKind.None or RefKind.In or RefKind.RefReadOnlyParameter)) | ||
{ | ||
return false; | ||
} | ||
|
||
// If the method is generic, can the type arguments be inferred from the one or two arguments? | ||
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. |
||
var typeParameters = method.TypeParameters; | ||
if (typeParameters.Length > 0) | ||
{ | ||
var usedParameterTypes = parameters.Slice(0, requiredLength).SelectAsArray(p => p.Type); | ||
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. Changed to 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. |
||
foreach (var typeParameter in typeParameters) | ||
{ | ||
if (!usedParameterTypes.Any((parameterType, typeParameter) => parameterType.ContainsTypeParameter(typeParameter), typeParameter)) | ||
{ | ||
// The type parameter does not appear in any of the parameter types. | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
|
||
/// <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 | ||
/// collection initializer node. Otherwise, return the element as is. | ||
/// </summary> | ||
internal static BoundExpression? GetUnderlyingCollectionExpressionElement(BoundCollectionExpression expr, BoundExpression? element) | ||
internal static BoundExpression GetUnderlyingCollectionExpressionElement(BoundCollectionExpression expr, BoundExpression element, bool throwOnErrors) | ||
{ | ||
if (expr.CollectionTypeKind is CollectionExpressionTypeKind.ImplementsIEnumerable) | ||
{ | ||
return element switch | ||
switch (element) | ||
{ | ||
BoundCollectionElementInitializer collectionInitializer => getCollectionInitializerElement(collectionInitializer), | ||
BoundDynamicCollectionElementInitializer dynamicInitializer => dynamicInitializer.Arguments[0], | ||
_ => null, | ||
}; | ||
case BoundCollectionElementInitializer collectionInitializer: | ||
return getCollectionInitializerElement(collectionInitializer); | ||
case BoundDynamicCollectionElementInitializer dynamicInitializer: | ||
return dynamicInitializer.Arguments[0]; | ||
} | ||
if (throwOnErrors) | ||
{ | ||
throw ExceptionUtilities.UnexpectedValue(element); | ||
} | ||
switch (element) | ||
{ | ||
case BoundCall call: | ||
return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0]; | ||
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.
This logic looks suspicious. It is not obvious what are we trying to accomplish and why. It looks like 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 feels like it would be better to place this special logic into 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. This case is to unwrap the 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.
Thanks for the clarification. Consider adding a comment what the code is trying to accomplish and why it makes sense to do what it is doing. |
||
default: | ||
return element; | ||
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. My expectation is the 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. Strengthened checks based on offline discussion. |
||
} | ||
} | ||
return element; | ||
|
||
|
@@ -1422,8 +1513,9 @@ private void GenerateImplicitConversionErrorForCollectionExpression( | |
} | ||
|
||
if (elements.Length > 0 && | ||
!HasCollectionExpressionApplicableAddMethod(node.Syntax, targetType, elementType, addMethods: out _, diagnostics)) | ||
!HasCollectionExpressionAddMethod(node.Syntax, targetType)) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_CollectionExpressionMissingAdd_New, node.Syntax, targetType); | ||
reportedErrors = true; | ||
} | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
I am not sure how easy it will be to come up with a scenario making importance of this obvious, but I think this helper should keep track of all dependencies (ref assemblies) that were involved on the path to success and make the caller aware of that set. I do not think the set must be explicitly narrowed, i.e. no reason to trim dependencies from "failed" attempts. Our primary concern is to not drop dependencies for "success". #Closed