-
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
Support 'foreach' for inline arrays. #68297
Support 'foreach' for inline arrays. #68297
Conversation
@cston, @dotnet/roslyn-compiler Please review. |
@cston, @dotnet/roslyn-compiler Please review. |
@@ -41,6 +41,9 @@ internal sealed class PrivateImplementationDetails : DefaultTypeDef, Cci.INamesp | |||
internal const string SynthesizedInlineArrayAsSpanName = "InlineArrayAsSpan"; | |||
internal const string SynthesizedInlineArrayAsReadOnlySpanName = "InlineArrayAsReadOnlySpan"; | |||
|
|||
internal const string SynthesizedInlineArrayElementRefName = "InlineArrayElementRef"; | |||
internal const string SynthesizedInlineArrayElementReadOnlyRefName = "InlineArrayElementReadOnlyRef"; |
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.
Should this be "RefReadOnly" instead?
This method is not meant for public consumption, We can call it whatever we want. Are you suggesting to change the name?
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.
Are you suggesting to change the name?
Yes. "RefReadOnly" would match the signature.
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. "RefReadOnly" would match the signature.
Renamed
checkingReceiver: false, | ||
diagnostics); | ||
} | ||
else |
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.
Are we testing this code path?
For example, Foreach_Variable_ReadOnly_02
and Foreach_Value_01
.
@@ -1967,6 +1967,46 @@ internal MethodSymbol EnsureInlineArrayAsReadOnlySpanExists(SyntaxNode syntaxNod | |||
return (MethodSymbol)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedInlineArrayAsReadOnlySpanName)!.GetInternalSymbol()!; | |||
} | |||
|
|||
internal MethodSymbol EnsureInlineArrayElementRefExists(SyntaxNode syntaxNode, NamedTypeSymbol intType, DiagnosticBag diagnostics) |
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.
It looks like these new methods and the methods above have the same boilerplate for the GetMethod(), TryAddSynthesizedMethod(), GetMethod() calls. Consider extracting a helper method that takes a method name and a delegate to create the method.
Since this refactoring going to include methods that are not related to the feature. I prefer to implement this refactoring in a separate PR, if there are no objections.
if (valueStore is object) | ||
{ | ||
collectionVarInitializationPreamble = rewriter._factory.ExpressionStatement(valueStore); | ||
preambleLocal = boundLocal.LocalSymbol; ; |
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.
BoundLocal boundLocal = rewriter._factory.StoreToTemp(rewrittenExpression, out BoundAssignmentOperator? valueStore); | ||
rewrittenExpression = boundLocal; | ||
|
||
if (valueStore is object) |
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.
} | ||
|
||
TypeSymbol inlineArrayType = boundArrayVar.Type; | ||
elementRef = elementRef.Construct(inlineArrayType, inlineArrayType.TryGetInlineArrayElementField()!.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.
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 the code for the 0-th ref TElement be emitted outside the loop in getPreamble, with getItem simply calling Unsafe.Add() to offset for the particular index?
I prefer to stick to the existing 'for' reawrite strategy where we always calculate element ref inside the loop and outside we have a local with a special SynthesizedLocalKind.ForEachArray
, which represents the array itself. This is likely important for debugging and EnC scenarios. I have a PROTOTYPE comment regarding this in MethodToStateMachineRewriter
. Happy to discuss this offline in more details.
And if so, would the PrivateImplementationDetails methods still be necessary?
Yes, they also reinterpret the type of the reference from array type to the element type.
What effect does In reply to: 1569473616 Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs:14623 in c3ffbf1. [](commit_id = c3ffbf1, deletion_comment = False) |
It doesn't have any effect, and the verification confirms that In reply to: 1569473616 Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs:14623 in c3ffbf1. [](commit_id = c3ffbf1, deletion_comment = False) |
@jjonescz, @dotnet/roslyn-compiler For the second review |
@@ -380,7 +380,7 @@ private BoundIndexerAccess BindIndexerDefaultArguments(BoundIndexerAccess indexe | |||
/// method returns a BoundBadExpression node. The method returns the original | |||
/// expression without generating any error if the expression has errors. | |||
/// </summary> | |||
private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) | |||
internal BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) |
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 protected
be enough? #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.
Would
protected
be enough?
It turns out this change isn't necessary after all
comp.VerifyDiagnostics( | ||
// (6,32): error CS8415: Asynchronous foreach statement cannot operate on variables of type 'Buffer4<int>' because 'Buffer4<int>' does not contain a public instance or extension definition for 'GetAsyncEnumerator'. Did you mean 'foreach' rather than 'await foreach'? | ||
// await foreach(var s in GetBuffer()) | ||
Diagnostic(ErrorCode.ERR_AwaitForEachMissingMemberWrongAsync, "GetBuffer()").WithArguments("Buffer4<int>", "GetAsyncEnumerator").WithLocation(6, 32) |
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 testing when there's a method GetAsyncEnumerator
(as instance or extension). #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.
Consider testing when there's a method
GetAsyncEnumerator
(as instance or extension).
I will add a test like that in one of the next PRs
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 will add a test like that in one of the next PRs
Since I needed to make some other change, I ended up adding a couple of tests below.
|
||
namespace System | ||
{ | ||
public readonly ref struct Span<T> |
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 adding a test with a Span<T>
where T
has some constraints. #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.
Consider adding a test with a Span where T has some constraints.
The Foreach_UnsupportedElementType
unit-test already covers constraint verification. Every generic type has implicit constraints. I do not think it is worth testing against a malformed Span type.
result = getEnumeratorInfo(ref span, isAsync: false, enumeratorInfoDiagnostics, out builder); | ||
|
||
#if DEBUG | ||
Debug.Assert(span == originalSpan); |
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 if Span<T>
is defined as a nullable class
, could this assert fail then? #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.
What if
Span<T>
is defined as a nullable class, could this assert fail then?
No, IsNullableType
returns true only for System.Nullable<T>
. The span
can never have type like that.
https://github.com/dotnet/csharplang/blob/main/proposals/inline-arrays.md#the-foreach-statement