Skip to content
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

Merged
merged 8 commits into from
Mar 12, 2024

Conversation

cston
Copy link
Member

@cston cston commented Mar 8, 2024

For a collection with an Add(params T[]) method, Binder.GetUnderlyingCollectionExpressionElement was returning the array rather than the element in the array.

Fixes #72461

@cston cston requested a review from a team as a code owner March 8, 2024 23:39
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 8, 2024
@cston cston marked this pull request as draft March 8, 2024 23:50
@cston cston marked this pull request as ready for review March 9, 2024 00:02
@RikkiGibson RikkiGibson self-assigned this Mar 9, 2024
RikkiGibson
RikkiGibson previously approved these changes Mar 9, 2024

comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe);
comp.VerifyEmitDiagnostics();
CompileAndVerify(comp, expectedOutput: "[], ");
Copy link
Contributor

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?

Copy link
Member Author

@cston cston Mar 11, 2024

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.

@RikkiGibson RikkiGibson dismissed their stale review March 9, 2024 01:31

meant to comment not approve yet

{
int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0;
var arg = collectionInitializer.Arguments[argIndex];
if (collectionInitializer.Expanded && collectionInitializer.AddMethod.Parameters[argIndex].IsParams)
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 9, 2024

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

Copy link
Member Author

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) { ... }

Copy link
Member Author

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.

Copy link
Contributor

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);
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add

Just curious, what method is called here? #Closed

Copy link
Member Author

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.

Copy link
Contributor

@AlekseyTs AlekseyTs Mar 11, 2024

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.

If that method is available and it is preferred over params version here, how do we end up using params method for collection expression?

Copy link
Member Author

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.

Copy link
Contributor

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.

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct.

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 2)

@AlekseyTs
Copy link
Contributor

@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);
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public override void Add(object[] x) => _list.AddRange(x);

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

@AlekseyTs
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences);

It looks like correctness leg complains about formatting issue around this line.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 6)

@cston cston merged commit 94c65cd into dotnet:main Mar 12, 2024
24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Mar 12, 2024
@RikkiGibson RikkiGibson removed this from the Next milestone Mar 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom IEnumarable<T>.Add(IEnumerable<T>) + Collection Initalizer causes csc.exe to crash
4 participants