-
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
Initial binding and lowering of collection literals with single-valued elements #67025
Initial binding and lowering of collection literals with single-valued elements #67025
Conversation
13dd522
to
f1226a9
Compare
_ = BindValue(spreadElementSyntax.Expression, diagnostics, BindValueKind.RValue); | ||
// PROTOTYPE: Temporary error until feature is implemented. | ||
Error(diagnostics, ErrorCode.ERR_InvalidExprTerm, syntax, syntax); | ||
return BadExpression(syntax); |
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.
nit: consider specific errors here to make it clear that these just aren't supported currently. saying that it's an invalid-expr seems like it could be confusing for anyone trying this out. #Resolved
<Field Name="Placeholder" Type="BoundObjectOrCollectionValuePlaceholder" Null="disallow" SkipInVisitor="true"/> | ||
<!-- Collection literal elements. --> | ||
<Field Name="Initializers" Type="ImmutableArray<BoundExpression>"/> | ||
</Node> |
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 me, it makes sense to perhaps have an abstract base type for the BoundCollectionLiteralExpression and a few specific concrete subclasses for the different cases (Array/span, vs collection-initializer). That way you don't have to know which properties are value for which types of collection literals. #Resolved
} | ||
|
||
if (node.SpanConstructor is { } spanConstructor) |
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.
nit : else if
and else
below to make it clear each branch is independent and does not impact the other.
@@ -57,6 +57,11 @@ public override BoundNode VisitConversion(BoundConversion node) | |||
BoundDelegateCreationExpression { WasTargetTyped: true }); | |||
|
|||
return objectCreation; | |||
|
|||
case ConversionKind.CollectionLiteral: | |||
// Skip through target-typed collection literals |
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.
nit: doc why skipping through is correct. (unclear to me).
bool isImplicit = boundCollectionLiteralExpression.WasCompilerGenerated; | ||
ImmutableArray<IOperation> elementValues = CreateFromArray<BoundExpression, IOperation>(boundCollectionLiteralExpression.Initializers); | ||
IOperation collectionCreation; | ||
if (collectionType is IArrayTypeSymbol arrayType) |
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.
nit: consider extracting all these branches into a helper method. then that helper method just returns the IOp for collection creation, and each branch just does an unconditional 'return'. it makes it easier to understand versus having to see all these potentially mutate local state and then track that flow to the final line. #Resolved
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.
Unfortunately, we return new NoneOperation()
directly in the else
so we'd need to check the result of helper method to avoid wrapping in a CollectionLiteralOperation
. Perhaps we can extract the helper method once the else
case (the natural type case) is supported.
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.
Done review pass (commit 5). Did not go through tests yet.
@@ -1094,6 +1094,9 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi | |||
|
|||
case BoundKind.UnconvertedObjectCreationExpression: | |||
return Conversion.ObjectCreation; | |||
|
|||
case BoundKind.UnconvertedCollectionLiteralExpression: |
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 would have expected to see changes to ClassifyStandardImplicitConversion
, or IsStandardImplicitConversion
. We probably have a test hole around use of a collection literal as an input to a user-defined conversion. #Resolved
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.
Will this become an issue once collection literals have natural types, or even within this change? I've added a test with a user-defined conversion to catch this case once natural types are supported. Let me know if I'm overlooking something.
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.
Likely will require the natural type.
var arrayType = collectionType as ArrayTypeSymbol; | ||
if (arrayType is 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.
var arrayType = collectionType as ArrayTypeSymbol; | |
if (arrayType is null) | |
if (collectionType is not ArrayTypeSymbol arrayType) |
@@ -57,6 +57,11 @@ public override BoundNode VisitConversion(BoundConversion node) | |||
BoundDelegateCreationExpression { WasTargetTyped: true }); | |||
|
|||
return objectCreation; | |||
|
|||
case ConversionKind.CollectionLiteral: |
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.
This implies that we're going to need to do some work in the semantic model for returning type and converted type info. #Resolved
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.
Thanks, added.
</Comments> | ||
<Property Name="CollectionCreation" Type="IOperation"> | ||
<Comments> | ||
<summary>Collection creation.</summary> |
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.
What does this mean? #Resolved
@@ -6340,6 +6340,23 @@ IArrayInitializerOperation popAndAssembleArrayInitializerValues(IArrayInitialize | |||
} | |||
} | |||
|
|||
// PROTOTYPE: Add tests. |
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.
More than that, the lowering needs to be implemented. We do not leave collection initializers as literals for IOperation today, and I would not expect us to do that here either. #Resolved
@@ -300,6 +304,7 @@ public CSharpOperationFactory(SemanticModel semanticModel) | |||
case BoundKind.StackAllocArrayCreation: | |||
case BoundKind.TypeExpression: | |||
case BoundKind.TypeOrValueExpression: | |||
case BoundKind.UnconvertedCollectionLiteralExpression: // PROTOTYPE: Is this 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.
No, I would expect this to be either a ICollectionLiteralOperation with a null type, or a IInvalidOperation
. NoneOperation
s are just for things that we do not support, and we should not be adding anything to this list. #Resolved
src/Compilers/CSharp/Test/Semantic/Semantics/CollectionLiteralTests.cs
Outdated
Show resolved
Hide resolved
@@ -1094,6 +1094,9 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi | |||
|
|||
case BoundKind.UnconvertedObjectCreationExpression: | |||
return Conversion.ObjectCreation; | |||
|
|||
case BoundKind.UnconvertedCollectionLiteralExpression: |
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.
Likely will require the natural type.
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 14)
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.
Overall LGTM so far. Had a few comments and questions. Haven't reviewed the tests yet.
@@ -631,6 +632,11 @@ public bool IsObjectCreation | |||
} | |||
} | |||
|
|||
/// <summary> | |||
/// Returns true if the conversion is an implicit collection creation expression conversion. |
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.
nit: "collection literal expression conversion" #Resolved
@@ -3706,6 +3706,10 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres | |||
var switchExpr = (BoundSwitchExpression)expr; | |||
return GetValEscape(switchExpr.SwitchArms.SelectAsArray(a => a.Value), scopeOfTheContainingExpression); | |||
|
|||
case BoundKind.ArrayOrSpanCollectionLiteralExpression: | |||
case BoundKind.CollectionInitializerCollectionLiteralExpression: | |||
return CallingMethodScope; |
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 would expect a prototype comment here. If the collection literal is converted to span I would expect us to use the local scope depth where the literal appears, or some other reasonable value like that. #Resolved
@@ -4184,6 +4188,10 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF | |||
|
|||
return true; | |||
|
|||
case BoundKind.ArrayOrSpanCollectionLiteralExpression: | |||
case BoundKind.CollectionInitializerCollectionLiteralExpression: | |||
return true; |
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.
would also expect a prototype comment here. e.g. void M(out Span<int> span) => span = [1, 2, 3]
should be a ref safety error. #Resolved
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.
Added a PROTOTYPE comment to GetValEscape()
and CheckValEscape()
. As I understand the proposal though, span = [1, 2, 3]
should not result an error because the array would be allocated on the heap in that case.
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.
Discussed offline, I'm basically willing to buy in to this line of thinking. I want LDM to discuss the possibility of a stackalloc [1, 2, 3]
expression. This would let users avoid inadvertently changing their code to introduce heap allocations.
BindingDiagnosticBag diagnostics) | ||
{ | ||
var syntax = (CSharpSyntaxNode)node.Syntax; | ||
var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true }; |
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 wasn't sure what the effect of WasCompilerGenerated = true
is here. #Resolved
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.
This matches BindInitializerExpression
, and seems reasonable to me since the placeholder is not in the source.
} | ||
collectionLiteral = bindArrayOrSpan(syntax, spanConstructor: null, implicitReceiver, node.Initializers, arrayType.ElementType, diagnostics); | ||
} | ||
else if (isSpanType(targetType, WellKnownType.System_Span_T, out elementType)) |
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.
Should there be a prototype comment to track stack-allocating collection literals converted to span? I'm not opposed to us punting on that in the initial implementation pass, though I'd probably want the ref safety rules to work as if the collection literal is stack-allocated.
My expectations around this area are based on reading https://github.com/dotnet/csharplang/blob/main/proposals/collection-literals.md#span-types. Let me know if that's not up to date. #Resolved
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.
Added a comment to GetValEscape()
and CheckValEscape()
for now.
{ | ||
// PROTOTYPE: If we support _inExpressionLambda, see VisitNewT() | ||
// which does not call MakeNewT() in that case. | ||
rewrittenReceiver = MakeNewT(syntax, typeParameter); |
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 didn't understand why we need a separate path for type parameters here instead of being able to use a CollectionCreation node in all cases. #Resolved
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.
Updated to use CollectionCreation
in all cases, thanks.
} | ||
|
||
// Create a temp for the collection. | ||
BoundAssignmentOperator assignmentToTemp; |
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.
nit: consider using 'out var'.
case BoundCollectionElementInitializer element: | ||
rewrittenInitializer = MakeCollectionInitializer(temp, element); | ||
break; | ||
default: |
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.
feels like this would read just a little more clearly as if (initializer is not BoundCollectionElementInitializer element) { throw; } rewrittenInitializer = MakeCollectionInitializer(temp, element);
. Unless is this is planned to be expanded later? #Resolved
} | ||
else | ||
{ | ||
var array = new ArrayCreationOperation( |
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 this is "tipping our hand" that the span is backed by an array, when the implementation might not always work this way. Is that fine? #Resolved
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.
Added a PROTOTYPE comment.
"; | ||
var expectedDiagnostics = new DiagnosticDescription[] | ||
{ | ||
// (6,13): error CS0815: Cannot assign collection literals to an implicitly-typed variable |
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.
This error will go away eventually, right? #Resolved
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, this error should disappear when support for natural type is added.
static void Main() | ||
{ | ||
object[,] x = []; | ||
int[,] y = [null, 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.
It feels like people may attempt to do int[,] y = [[1, 2], [3, 4]]
(if I'm recalling/understanding what that notations means correctly). Consider also testing that. #Resolved
"""; | ||
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); | ||
comp.VerifyDiagnostics( | ||
// (6,20): error CS8347: Cannot use a result of 'Program.F2<int>(in Span<int>)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope |
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.
Consider also testing 'scoped in {{spanType}} s`. #Resolved
Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "2").WithArguments("S", "Add").WithLocation(3, 9)); | ||
} | ||
|
||
[Fact] |
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.
Have to pause here to have a meeting, will resume later in afternoon.
…nto collections-1
} | ||
"""; | ||
var comp = CreateCompilation(source); | ||
// PROTOTYPE: // 2 should be reported as an error (compare with array initializer: new object[] { 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.
Did you mean reported as a warning? #Resolved
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, corrected, thanks.
"""; | ||
var comp = CreateCompilation(source); | ||
comp.VerifyEmitDiagnostics( | ||
// (13,24): error CS9500: Cannot initialize type 'S<object>?' with a collection literal because the type is not constructible. |
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 didn't understand why this case was erroneous. #Resolved
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 target type is Nullable<S<object>>
which is not considered constructible because it does not implement IEnumerable
.
"""); | ||
} | ||
|
||
// Ensure collection literal conversions are not standard implicit conversions |
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.
Is there discussion I can review which explains why we want this? #Resolved
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 only discussion was in #67025 (comment). I have added an open question to the test plan #66418.
Proposal: collection-literals.md
Test plan: #66418