Collection expression arguments: support [CollectionBuilder] factory methods#76998
Conversation
da3c3e3 to
6c4fc7e
Compare
| 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), |
There was a problem hiding this comment.
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.
242f3ea to
399140a
Compare
399140a to
f826cdb
Compare
d8277bf to
fb0b649
Compare
fb0b649 to
c45c03a
Compare
fd9db56 to
e6301bb
Compare
e8f7fdd to
9c80498
Compare
9c80498 to
3e4b545
Compare
2030034 to
50db4a3
Compare
| 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])); |
There was a problem hiding this comment.
The second check should not fail. Changed to an assert.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Why was the assertion removed? Looking at conversion logic, I don't see any changes affecting collectionCreation outside of collection builder scenario. #Closed
There was a problem hiding this comment.
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.
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 20). Tests not looked at yet
333fred
left a comment
There was a problem hiding this comment.
Done review pass. Didn't go into the tests yet.
| internal partial class BoundCollectionExpressionWithElement | ||
| { | ||
| internal void GetArguments(AnalyzedArguments analyzedArguments) | ||
| internal void AddArguments(AnalyzedArguments analyzedArguments) |
There was a problem hiding this comment.
Perhaps AddSelfToArguments? The current wording implies that it's modifying this, which is not happening. #Closed
There was a problem hiding this comment.
Renamed to AddToArguments.
| { | ||
| if (element is BoundCollectionExpressionWithElement withElement) | ||
| { | ||
| // PROTOTYPE: If there are multiple with() elements, should with() elements after |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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])); |
There was a problem hiding this comment.
Also, can this either be a ReferenceEquals, or be passed some TypeCompareKind?
| 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), |
There was a problem hiding this comment.
What's caused this to get worse in this PR?
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) |
Builder methods are In reply to: 2672483630 Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:1127 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False) |
Existing tests in In reply to: 2672531426 Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionArgumentTests.cs:4114 in 9965c1b. [](commit_id = 9965c1b, deletion_comment = False) |
11a1c39
into
dotnet:features/dictionary-expressions
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