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

Initial binding and lowering of collection literals with single-valued elements #67025

Merged
merged 19 commits into from
Mar 14, 2023

Conversation

cston
Copy link
Member

@cston cston commented Feb 23, 2023

Proposal: collection-literals.md
Test plan: #66418

@cston cston force-pushed the collections-1 branch 8 times, most recently from 13dd522 to f1226a9 Compare February 27, 2023 06:08
_ = BindValue(spreadElementSyntax.Expression, diagnostics, BindValueKind.RValue);
// PROTOTYPE: Temporary error until feature is implemented.
Error(diagnostics, ErrorCode.ERR_InvalidExprTerm, syntax, syntax);
return BadExpression(syntax);
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Feb 27, 2023

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&lt;BoundExpression&gt;"/>
</Node>
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Feb 27, 2023

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

Comment on lines 27 to 29
}

if (node.SpanConstructor is { } spanConstructor)
Copy link
Member

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
Copy link
Member

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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Feb 27, 2023

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

Copy link
Member Author

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.

@cston cston marked this pull request as ready for review March 1, 2023 00:28
@cston cston requested review from a team as code owners March 1, 2023 00:28
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 (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:
Copy link
Member

@333fred 333fred Mar 1, 2023

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

Copy link
Member Author

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.

Copy link
Member

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.

Comment on lines +24 to +25
var arrayType = collectionType as ArrayTypeSymbol;
if (arrayType is null)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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:
Copy link
Member

@333fred 333fred Mar 1, 2023

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

Copy link
Member Author

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>
Copy link
Member

@333fred 333fred Mar 1, 2023

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.
Copy link
Member

@333fred 333fred Mar 1, 2023

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?
Copy link
Member

@333fred 333fred Mar 1, 2023

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. NoneOperations are just for things that we do not support, and we should not be adding anything to this list. #Resolved

@RikkiGibson RikkiGibson self-assigned this Mar 1, 2023
@@ -1094,6 +1094,9 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi

case BoundKind.UnconvertedObjectCreationExpression:
return Conversion.ObjectCreation;

case BoundKind.UnconvertedCollectionLiteralExpression:
Copy link
Member

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.

@cston cston requested review from RikkiGibson and a team March 8, 2023 19:31
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.

LGTM (commit 14)

Copy link
Contributor

@RikkiGibson RikkiGibson left a 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.
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 9, 2023

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

@RikkiGibson RikkiGibson Mar 9, 2023

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

@RikkiGibson RikkiGibson Mar 9, 2023

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

Copy link
Member Author

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.

Copy link
Contributor

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

@RikkiGibson RikkiGibson Mar 9, 2023

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

Copy link
Member Author

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

@RikkiGibson RikkiGibson Mar 9, 2023

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

Copy link
Member Author

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

@RikkiGibson RikkiGibson Mar 10, 2023

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

Copy link
Member Author

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

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:
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 10, 2023

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(
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 10, 2023

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

Copy link
Member Author

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

@RikkiGibson RikkiGibson Mar 13, 2023

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

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, this error should disappear when support for natural type is added.

static void Main()
{
object[,] x = [];
int[,] y = [null, 2];
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 13, 2023

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

@RikkiGibson RikkiGibson Mar 13, 2023

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]
Copy link
Contributor

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.

}
""";
var comp = CreateCompilation(source);
// PROTOTYPE: // 2 should be reported as an error (compare with array initializer: new object[] { null }).
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 13, 2023

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

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, 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.
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 13, 2023

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

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

@RikkiGibson RikkiGibson Mar 13, 2023

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

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 only discussion was in #67025 (comment). I have added an open question to the test plan #66418.

@cston cston merged commit 02f55f6 into dotnet:features/CollectionLiterals Mar 14, 2023
@cston cston deleted the collections-1 branch March 14, 2023 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants