-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Add IOp support for with(...) elements.
#81058
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
base: features/collection-expression-arguments
Are you sure you want to change the base?
Add IOp support for with(...) elements.
#81058
Conversation
…-arguments' into creationArguments
src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs
Outdated
Show resolved
Hide resolved
|
|
||
| var operation = semanticModel.GetOperation(root.DescendantNodes().OfType<CollectionExpressionSyntax>().ToArray()[1]); | ||
| VerifyOperationTree(compilation, operation, """ | ||
| ICollectionExpressionOperation (1 elements, ConstructMethod: MyList<System.Int32>..ctor([System.Int32 capacity = 0], [System.String name = "default"])) (OperationKind.CollectionExpression, Type: MyList<System.Int32>) (Syntax: '[with(capacity: 10), 2]') |
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.
review with whitespace off.
| { | ||
| EvalStackFrame frame = PushStackFrame(); | ||
| var creationArguments = VisitArguments(operation.CreationArguments, instancePushed: false); | ||
| PopStackFrame(frame); |
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 seemed sensible to me to push/pop a frame here independent of the frame for visiting the elements. But i wasn't 100% sure. LMK if this is correct.
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'm not sure what the decision to keep the frames for the arguments and elements "independent" versus nested like in VisitObjectCreation could impact.
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.
Maybe Aleksey can advise. Otherwise this does need some more research
I suspect we can do await with the split
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.
note: my intent was to match VisitObjectCreation.
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.
note: my intent was to match VisitObjectCreation.
Thanks for the clarification
In that case, would it be an outer frame with an inner frame (push, push, pop, pop) instead of two separate frames (push, pop, push, pop)?
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.
Maybe Aleksey can advise.
See my other comments
| </summary> | ||
| </Comments> | ||
| </Property> | ||
| <Property Name="CreationArguments" Type="ImmutableArray<IArgumentOperation>"> |
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 went with CreationArguments as i think it's nicely 'generic'. They are the arguments that control the 'creation' of the collection. I didn't really want to be very explicit that they were the 'WithElement' arguments. But i don't feel strongly on this. If people think it should align more closely with the syntax, that's ok with me.
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 feels like ConstructArguments to emphasize the connection with ConstructMethod would be reasonable here. However, I also don't feel strongly. :)
|
@RikkiGibson @jjonescz @333fred ptal. |
| // (12,74): warning CS8620: Argument of type 'T' cannot be used for parameter 'items' of type 'ReadOnlySpan<T?>' in 'MyCollection<T?> MyCollectionBuilder.Create<T?>(ReadOnlySpan<T?> items)' due to differences in the nullability of reference types. | ||
| // static IMyCollection<T?> F<T>(ReadOnlySpan<T> items, T arg) => [with(arg), ..items]; | ||
| Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "arg").WithArguments("T", "System.ReadOnlySpan<T?>", "items", "MyCollection<T?> MyCollectionBuilder.Create<T?>(ReadOnlySpan<T?> items)").WithLocation(12, 74)); | ||
| Diagnostic(ErrorCode.ERR_BadCollectionArgumentsArgCount, "with(arg)").WithArguments("Create", "1").WithLocation(12, 69)); |
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.
note: this now matches CollectionBuilder_SpreadElement_BoxingConversion_A, which also has no nullable warning here. the only error we should report is the bad-with, since we only bind to (ReadOnlySpan<T> items) not (ReadOnlySpan<T> items, T arg) (since arguments can't go at builder end, they must be at the start).
|
Closes #80998 ? |
src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs
Outdated
Show resolved
Hide resolved
|
Assert added in d4da134 |
| ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.List<System.Int32>..ctor(System.Int32 capacity)) (OperationKind.CollectionExpression, Type: System.Collections.Generic.IList<System.Int32>) (Syntax: '[with(Compu ... seBranch()]') | ||
| ConstructArguments(1): | ||
| IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: capacity) (OperationKind.Argument, Type: null) (Syntax: 'ComputeCapacity()') | ||
| IInvocationOperation (System.Int32 Program.ComputeCapacity()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'ComputeCapacity()') |
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.
| if (operation.ConstructArguments.Any(a => a is IArgumentOperation) && !operation.ConstructArguments.All(a => a is IArgumentOperation)) | ||
| throw ExceptionUtilities.UnexpectedValue("Mixed argument operations and non-argument operations in ConstructArguments"); | ||
|
|
||
| // Ugly, but necessary. If we bound successfully, we'll have an array of IArgumentOperation. We want to |
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.
| ? PopArray(operation.ConstructArguments) | ||
| : ImmutableArray<IOperation>.CastUp(PopArray(arguments, RewriteArgumentFromArray)); | ||
|
|
||
| return PopStackFrame(frame, new CollectionExpressionOperation( |
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.
To clarify, this refers to the change around the following original lines:
PopStackFrame(frame);
return new CollectionExpressionOperation(
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 find this easier to read and reason about as it prevents anything from sneaking in between the popping of the stack frame and the returning of the final collection expr op. I would prefer to keep it in this form as i think it better represents the semantics wanted here.
| // creation methods when producing the .ConstructArguments for the ICollectionExpressionOperation. | ||
| // Note: the caller will end up stripping this off when producing the ConstructArguments, so it will | ||
| // not actually leak to the user. But this ends up keeping the logic simple between that callsite | ||
| // and this code which actually hits all the arguments passed along. Because we never actually |
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 find this approach bug-prone and, at the moment, I am not convinced that it is the right "price" for the simplification
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.
Do you have a recommendation on an alternative approach?
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.
Do you have a recommendation on an alternative approach?
I am assuming this is about ReadOnlySpan placeholder and leaning towards the following:
- It will be cleaner (and probably simpler) to leave the node in IOperation tree
- Bound tree should use a placeholder node that cannot be confused with a placeholder used for any other purpose. For example, by adding a placeholder kind to
ValuePlaceholder, or by using distinct bound node type. - For that node, we probably should create
IPlaceholderOperationwith newPlaceholderKind, or add a whole new IOperation node - Stack spilling in CFG builder should be adjusted to to leave the placeholder on the stack unchanged
| if (boundCall.IsErroneousNode) | ||
| { | ||
| var array = @this.CreateFromArray<BoundNode, IOperation>(((IBoundInvalidNode)boundCall).InvalidNodeChildren); | ||
| return array.WhereAsArray(o => o is not IPlaceholderOperation); |
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.
| if (collectionCreation is null) | ||
| return []; | ||
|
|
||
| Debug.Fail("Unhandled case: " + collectionCreation.GetType()); |
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.
Nice. 7bdad0d
|
|
||
| // With a CollectionBuilder, the last argument will be a placeholder where the .Elements will go. | ||
| // We do *not* want to include that information in the Arguments we return. | ||
| Debug.Assert(arguments is [.., IArgumentOperation { Value: IPlaceholderOperation { PlaceholderKind: PlaceholderKind.Unspecified } }], "We should always have at least one argument (the placeholder elements)."); |
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.
SGTM: 9727a59
It will be interesting to check the shape of IOperation tree for this collection expression and see how Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests_WithElement_ArraysAndSpans.cs:578 in b21f43c. [](commit_id = b21f43c, deletion_comment = False) |
|
… On Thu, Nov 13, 2025 at 5:28 PM AlekseyTs ***@***.***> wrote:
*AlekseyTs* left a comment (dotnet/roslyn#81058)
<#81058 (comment)>
Span<int> span = [with(in x), 1, 2, 3];
It will be interesting to check the shape of IOperation tree for this
collection expression and see how x is reflected in it.
------------------------------
Refers to:
src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests_WithElement_ArraysAndSpans.cs:578
in b21f43c
<b21f43c>.
[](commit_id = b21f43c
<b21f43c>,
deletion_comment = False)
—
Reply to this email directly, view it on GitHub
<#81058 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY5QIPZ3VGG5TKPJWWT34SWS3AVCNFSM6AAAAACLIBKJ32VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTKMRYGYZTANRRGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
| namespace Microsoft.CodeAnalysis.CSharp.UnitTests; | ||
|
|
||
| [CompilerTrait(CompilerFeature.CollectionExpressions)] | ||
| public sealed class CollectionExpressionTests_WithElement_IOperation : CSharpTestBase |
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.
Please move this file to Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests project. It is fine to add IOperation/CFG verification to some of the existing tests to assert more for those scenarios. However, we prefer all tests with the sole purpose to verify IOperation/CFG to be placed to the dedicated test project.
| ErrorCode.ERR_CollectionArgumentsNotSupportedForType, | ||
| _node.WithElement.Syntax.GetFirstToken().GetLocation(), | ||
| _targetType); | ||
| return null; |
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.
| // For the read-only array interfaces (IEnumerable<E>, IReadOnlyCollection<E>, IReadOnlyList<E>), only | ||
| // the parameterless `with()` is allowed. | ||
| _diagnostics.Add(ErrorCode.ERR_CollectionArgumentsMustBeEmpty, _node.WithElement.Syntax.GetFirstToken().GetLocation()); | ||
| return null; |
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.
|
… On Thu, Nov 13, 2025 at 5:38 PM AlekseyTs ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In
src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests_WithElement_IOperation.cs
<#81058 (comment)>:
> @@ -0,0 +1,1017 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.CSharp.UnitTests;
+
+[CompilerTrait(CompilerFeature.CollectionExpressions)]
+public sealed class CollectionExpressionTests_WithElement_IOperation : CSharpTestBase
CollectionExpressionTests_WithElement_IOperation
<http://example.com/codeflow?start=20&length=48>
Please move this file to
Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests project. It is fine to
add IOperation/CFG verification to some of the existing tests to assert
more for those scenarios. However, we prefer all tests with the sole
purpose to verify IOperation/CFG to be placed to the dedicated test project.
—
Reply to this email directly, view it on GitHub
<#81058 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY6UA3HPROGDGK3MZVD34SXZRAVCNFSM6AAAAACLIBKJ32VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTINRQGY4DSMBWHE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
Done with review pass (commit 54), only glanced over the test changes |
Relates to test plan #80613
Closes #80998