Skip to content

Collection expression arguments: support [CollectionBuilder] factory methods#76998

Merged
cston merged 27 commits intodotnet:features/dictionary-expressionsfrom
cston:collection-builder-args
Feb 20, 2025
Merged

Collection expression arguments: support [CollectionBuilder] factory methods#76998
cston merged 27 commits intodotnet:features/dictionary-expressionsfrom
cston:collection-builder-args

Conversation

@cston
Copy link
Contributor

@cston cston commented Feb 1, 2025

Binding and lowering of collection expression arguments for target types that are constructed using [CollectionBuilder] factory methods.

See proposals/collection-expression-arguments.md

Relates to test plan #76310

@ghost ghost added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Feb 1, 2025
@cston cston force-pushed the collection-builder-args branch from da3c3e3 to 6c4fc7e Compare February 2, 2025 01:13
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, "[null]").WithArguments("MyCollectionBuilder.Create<T>(System.ReadOnlySpan<T>)", "T", "string?").WithLocation(7, 28),
//// (7,28): warning CS8714: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'MyCollectionBuilder.Create<T>(ReadOnlySpan<T>)'. Nullability of type argument 'string?' doesn't match 'notnull' constraint.
//// MyCollection<string?> x1 = [null]; // 1
//Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, "[null]").WithArguments("MyCollectionBuilder.Create<T>(System.ReadOnlySpan<T>)", "T", "string?").WithLocation(7, 28),
Copy link
Contributor Author

@cston cston Feb 2, 2025

Choose a reason for hiding this comment

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

WRN_NullabilityMismatchInTypeParameterNotNullConstraint

This case was previously handled in Binder.GetAndValidateCollectionBuilderMethod() in the call to collectionBuilderMethod.CheckConstraints(). That was unintentional, because initial binding should check type constraints but not nullability constraints. Nullability constraints should be checked in NullableWalker, and the existing work item in #68786 covers that.

@cston cston force-pushed the collection-builder-args branch from 242f3ea to 399140a Compare February 4, 2025 17:41
@cston cston force-pushed the collection-builder-args branch from 399140a to f826cdb Compare February 4, 2025 19:41
@cston cston force-pushed the collection-builder-args branch 3 times, most recently from d8277bf to fb0b649 Compare February 7, 2025 18:59
@cston cston force-pushed the collection-builder-args branch from fb0b649 to c45c03a Compare February 7, 2025 19:08
@cston cston force-pushed the collection-builder-args branch from fd9db56 to e6301bb Compare February 8, 2025 00:49
@cston cston force-pushed the collection-builder-args branch from e8f7fdd to 9c80498 Compare February 11, 2025 06:53
@cston cston force-pushed the collection-builder-args branch from 9c80498 to 3e4b545 Compare February 11, 2025 08:09
@cston cston force-pushed the collection-builder-args branch from 2030034 to 50db4a3 Compare February 12, 2025 21:47
static bool usesSingleParameterBuilderMethod(CSharpCompilation compilation, BoundCollectionExpression node, TypeWithAnnotations elementType)
{
return Binder.GetCollectionBuilderMethod(node) is { Parameters: [var parameter] } &&
parameter.Type.Equals(compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T).Construct([elementType]));
Copy link
Member

@jcouv jcouv Feb 18, 2025

Choose a reason for hiding this comment

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

parameter.Type.Equals(compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T).Construct([elementType]))

Do we have a test for the second part of the condition? I"m not sure when it would come into play #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The second check should not fail. Changed to an assert.

Copy link
Member

Choose a reason for hiding this comment

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

Also, can this either be a ReferenceEquals, or be passed some TypeCompareKind?

case CollectionExpressionTypeKind.ImplementsIEnumerable:
if (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Generic_List_T, out var listElementType))
if (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Generic_List_T, out var listElementType) &&
usesParameterlessListConstructor(_compilation, node))
Copy link
Contributor Author

@cston cston Feb 18, 2025

Choose a reason for hiding this comment

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

usesParameterlessListConstructor(_compilation, node))

The local function usesParameterlessListConstructor() was extracted from useListOptimization() and used here, to avoid calling TryRewriteSingleElementSpreadToList() when arguments are included with a List<T> target type. See List_SingleSpread() unit test which would fail to set capacity: in [with(capacity: capacity), ..e] otherwise.

This change affects List<T> rather than collection builder types.

{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(_additionalLocals is { });
Debug.Assert(node.CollectionCreation is null); // shouldn't have generated a constructor call
Copy link
Member

@jcouv jcouv Feb 18, 2025

Choose a reason for hiding this comment

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

Why was the assertion removed? Looking at conversion logic, I don't see any changes affecting collectionCreation outside of collection builder scenario. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

VisitArrayOrSpanCollectionExpression() is called from VisitCollectionBuilderCollectionExpression(), when generating the ReadOnlySpan<T> argument for the builder method, even though node represents the collection created from the builder in that case, rather than the argument.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 20). Tests not looked at yet

@jcouv jcouv self-assigned this Feb 18, 2025
Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Done review pass. Didn't go into the tests yet.

internal partial class BoundCollectionExpressionWithElement
{
internal void GetArguments(AnalyzedArguments analyzedArguments)
internal void AddArguments(AnalyzedArguments analyzedArguments)
Copy link
Member

@333fred 333fred Feb 18, 2025

Choose a reason for hiding this comment

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

Perhaps AddSelfToArguments? The current wording implies that it's modifying this, which is not happening. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed to AddToArguments.

{
if (element is BoundCollectionExpressionWithElement withElement)
{
// PROTOTYPE: If there are multiple with() elements, should with() elements after
Copy link
Member

Choose a reason for hiding this comment

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

Eh, we have the information. Feels to me like we can be useful to users and bind it, even if it's the second thing. For duplicate withs, I'm of less strong opinion, we could possibly just make it a bad node and leave it.

return elements.All(canOptimizeListElement, addMethod);
return ctor is { } &&
node.CollectionCreation is BoundObjectCreationExpression { Constructor: var objectCreate } &&
ctor.Equals(objectCreate.OriginalDefinition);
Copy link
Member

@333fred 333fred Feb 19, 2025

Choose a reason for hiding this comment

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

Can this be a ReferenceEquals? #Closed

static bool usesSingleParameterBuilderMethod(CSharpCompilation compilation, BoundCollectionExpression node, TypeWithAnnotations elementType)
{
return Binder.GetCollectionBuilderMethod(node) is { Parameters: [var parameter] } &&
parameter.Type.Equals(compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T).Construct([elementType]));
Copy link
Member

Choose a reason for hiding this comment

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

Also, can this either be a ReferenceEquals, or be passed some TypeCompareKind?

@cston cston requested review from a team, 333fred and jcouv February 19, 2025 20:11
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, "[null]").WithArguments("MyCollectionBuilder.Create<T>(System.ReadOnlySpan<T?>)", "T", "string?").WithLocation(7, 28),
//// (7,28): warning CS8714: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'MyCollectionBuilder.Create<T>(ReadOnlySpan<T?>)'. Nullability of type argument 'string?' doesn't match 'notnull' constraint.
//// MyCollection<string?> x1 = [null];
//Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, "[null]").WithArguments("MyCollectionBuilder.Create<T>(System.ReadOnlySpan<T?>)", "T", "string?").WithLocation(7, 28),
Copy link
Member

Choose a reason for hiding this comment

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

What's caused this to get worse in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jcouv
Copy link
Member

jcouv commented Feb 20, 2025

    public void CollectionBuilder_ParamsParameter()

Consider testing a scenario where the instance Create is not applicable or missing, but we have an extension method which is. If I recall, for collection builders, we only want instance Create methods, right? #Resolved


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:1127 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False)

@cston
Copy link
Contributor Author

cston commented Feb 20, 2025

    public void CollectionBuilder_ParamsParameter()

Builder methods are static methods that are found from the builder type. We don't consider methods outside of the builder type.


In reply to: 2672483630


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:1127 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False)

@jcouv
Copy link
Member

jcouv commented Feb 20, 2025

    }

Do we have any test targeting the RefSafetyAnalysis change?


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:4114 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 27)

@cston
Copy link
Contributor Author

cston commented Feb 20, 2025

    }

Existing tests in CollectionExpressionTests hit the affected code paths in RefSafetyAnalysis.VisitCollectionExpression() and RefSafetyAnalysis.HasLocalScope(). But we don't have any ref safety tests for arguments though. I will add tests in a follow up PR. Thanks.


In reply to: 2672531426


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:4114 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False)

@cston cston merged commit 11a1c39 into dotnet:features/dictionary-expressions Feb 20, 2025
28 checks passed
@cston cston deleted the collection-builder-args branch February 20, 2025 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Dictionary Expressions untriaged Issues and PRs which have not yet been triaged by a lead VSCode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants