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

Support 'foreach' for inline arrays. #68297

Merged
merged 7 commits into from
Jun 8, 2023

Conversation

@AlekseyTs AlekseyTs requested a review from cston May 22, 2023 21:39
@AlekseyTs AlekseyTs requested a review from a team as a code owner May 22, 2023 21:39
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label May 22, 2023
@AlekseyTs
Copy link
Contributor Author

@cston, @dotnet/roslyn-compiler Please review.

@AlekseyTs
Copy link
Contributor Author

@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";
Copy link
Member

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

ReadOnlyRef

Should this be "RefReadOnly" instead? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs May 26, 2023

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?

Copy link
Member

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.

Copy link
Contributor Author

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

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

else

Are we testing this code path? #Resolved

Copy link
Contributor Author

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

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

EnsureInlineArrayElementRefExists

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. #Pending

Copy link
Contributor Author

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

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

;

Extra ; #Resolved

BoundLocal boundLocal = rewriter._factory.StoreToTemp(rewrittenExpression, out BoundAssignmentOperator? valueStore);
rewrittenExpression = boundLocal;

if (valueStore is object)
Copy link
Member

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

if (valueStore is object)

It looks like StoreToTemp() always generates a non-null value. #Resolved

}

TypeSymbol inlineArrayType = boundArrayVar.Type;
elementRef = elementRef.Construct(inlineArrayType, inlineArrayType.TryGetInlineArrayElementField()!.Type);
Copy link
Member

@cston cston May 26, 2023

Choose a reason for hiding this comment

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

elementRef

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?

And if so, would the PrivateImplementationDetails methods still be necessary? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs May 26, 2023

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.

@cston
Copy link
Member

cston commented May 31, 2023

}

What effect does GetEnumerator() have?


In reply to: 1569473616


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs:14623 in c3ffbf1. [](commit_id = c3ffbf1, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

}

What effect does GetEnumerator() have?

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)

@AlekseyTs
Copy link
Contributor Author

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

@jjonescz jjonescz Jun 7, 2023

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

Copy link
Contributor Author

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

@jjonescz jjonescz Jun 8, 2023

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

Copy link
Contributor Author

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

Copy link
Contributor Author

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

@jjonescz jjonescz Jun 8, 2023

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

Copy link
Contributor Author

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

@jjonescz jjonescz Jun 8, 2023

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

Copy link
Contributor Author

@AlekseyTs AlekseyTs Jun 8, 2023

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.

@AlekseyTs AlekseyTs enabled auto-merge (squash) June 8, 2023 14:59
@AlekseyTs AlekseyTs merged commit cf124b4 into dotnet:features/InlineArrays Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers New Feature - Inline Arrays untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants