-
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: IOperation and Add method with params array #72469
Conversation
|
||
comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); | ||
comp.VerifyEmitDiagnostics(); | ||
CompileAndVerify(comp, expectedOutput: "[], "); |
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 expected the output here to be similar to the output for sourceB1
. Am I missing something about the semantics of the respective expressions?
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.
In sourceB1
, the Add(params MyCollection[])
call for x
in [x, ..y]
is bound in expanded form, that is we call the method with Add(new MyCollection[] { x })
.
In sourceB2
, the call for []
in [[]]
is bound in normal form, that is the []
is treated as an empty array rather than a MyCollection
instance and we call Add(new MyCollection[0])
.
I've added Console.WriteLine()
calls to hopefully indicate this.
{ | ||
int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0; | ||
var arg = collectionInitializer.Arguments[argIndex]; | ||
if (collectionInitializer.Expanded && collectionInitializer.AddMethod.Parameters[argIndex].IsParams) |
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.
&& collectionInitializer.AddMethod.Parameters[argIndex].IsParams
I think we should remove this check. At best it is redundant. because overload resolution already checked for that. At worse, it will lead to the same bug when the collectionInitializer.AddMethod
is an override that doesn't have ParamArray
attribute (overload resolution checks for it on the least derived declaration). The collectionInitializer.Expanded
condition is sufficient in my opinion. #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.
It's necessary to check that the argIndex
refers to the params
parameter even if the collectionInitializer.Expanded
to allow for the case where there is a parameter before the params
parameter (see Add_ParamsArray_04
):
public void Add(T x, params T[] y) { ... }
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.
It looks like the ParamArray
attribute is added to overrides, at least for overrides declared in C#. But I've changed the check here to avoid using IsParams
.
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.
It looks like the
ParamArray
attribute is added to overrides, at least for overrides declared in C#.
What is important is the fact that the presence of the attribute on overrides is not required for the method to be used in expanded form. And metadata like that can easily be created.
public static void Add<T>(this ICollection<T> collection, params T[] elements) | ||
{ | ||
foreach (T element in elements) | ||
collection.Add(element); |
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.
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.
The implementation of ICollection<T>.Add(T)
is called on the collection
instance.
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.
The implementation of
ICollection<T>.Add(T)
is called on thecollection
instance.
If that method is available and it is preferred over params
version here, how do we end up using params
method for collection expression?
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.
The ICollection<KeyValuePair<K, V>>.Add()
method on Dictionary<K, V>
is ignored in the collection expression because it is an explicit implementation.
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.
The
ICollection<KeyValuePair<K, V>>.Add()
method onDictionary<K, V>
is ignored in the collection expression because it is an explicit implementation.
In other words, there is no suitable Add
method on Dictionary<K, V>
, we start looking for an extension on Dictionary<K, V>
and find one that extends ICollection<KeyValuePair<K, V>>
. We never look for Add
on ICollection<KeyValuePair<K, V>>
. Is this an accurate description of what is going on?
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.
Yes, that's correct.
Done with review pass (commit 2) |
@cston Consider leaving the bug active and assigning it to me when this PR is merged. I will make sure similar scenarios with param collections are working properly. |
class MyCollection : MyCollectionBase, IEnumerable<object> | ||
{ | ||
private List<object> _list = new(); | ||
public override void Add(object[] x) => _list.AddRange(x); |
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.
It doesn't look like this test targets an interesting scenario when the override doesn't have ParamArray
attribute. Consider adding a test for this scenario as well. The situation can be achieved by placing abstract
and override
methods in different assemblies and adding params
modifier to the abstract
method after the override
method is compiled. #Closed
Done with review pass (commit 4) |
} | ||
"""; | ||
string assemblyName = GetUniqueName(); | ||
var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); |
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.
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.
LGTM (commit 6)
For a collection with an
Add(params T[])
method,Binder.GetUnderlyingCollectionExpressionElement
was returning the array rather than the element in the array.Fixes #72461