-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Binding for deconstruction-declaration in 'foreach' statement #12326
Conversation
It would be nice to have a test that confirms that these foreach loop iteration variables remain readonly, like the ones introduced the old way. |
@gafter I didn't know about that behavior. Thanks for pointing that out. |
I will be working from home, but I'm happy to chat with you whenever. |
@jcouv The readonly behavior would be my suggestion for these (since foreach variables are readonly) but I can imagine the LDM deciding either way. So don't necessarily rush to change it from whatever it is naturally in this implementation. Rather, note it as an issue. |
@@ -645,6 +649,11 @@ protected override TypeSymbol InferTypeOfVarVariable(DiagnosticBag diagnostics) | |||
loopBinder.BindDeconstructionDeclaration(forStatement.Declaration, forStatement.Declaration, diagnostics); | |||
break; | |||
|
|||
case SyntaxKind.ForEachStatement: | |||
var foreachBinder = this.binder.GetBinder((ForEachStatementSyntax)statement); | |||
foreachBinder.BindForEachDeconstruction(diagnostics, foreachBinder); |
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.
Isn't this.binder
the ForEachBinder
? If so, ForEachBinder.BindForEachDeconstruction
can be called directly and the method does not need to be virtual
.
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 entirely sure, but Aleksey warned against taking such shortcuts in this comment on my last PR.
In terms of binders, the above is staying as close as possible to what happens with BindForEach
(before it calls into BindForEachDeconstruction
).
The last commit fixes the circularity problem, using |
@CyrusNajmabadi @dotnet/roslyn-ide The last commit contains a fix for formatting deconstruction-declaration syntax, if you want to review. |
Assert.Null(model.GetSymbolInfo(x12Var.Type).Symbol); // The var in `var (x1, x2)` has no symbol | ||
}; | ||
|
||
var comp = CompileAndVerify(source, expectedOutput: "1 2 - 3 4 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); |
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.
we need to check IL here to make sure we are using array indexing directly.
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, I'll add that. I did verify manually in debugger though.
LGTM |
@cston When you have some time, a second review would be great. |
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); | ||
|
||
VariableDeclarationSyntax variables = _syntax.DeconstructionVariables; | ||
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, inferredType ?? CreateErrorType("var")); |
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 expression inferredType ?? CreateErrorType("var")
is used several times in ForEachLoopBinder
. Consider extracting a helper method.
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 looked at a few ways to improve this. Moving that logic into the method that produces inferredType
(but that didn't work due to some other consumers). I also tried extracting a method, but I didn't feel it helped.
So I'm leaving as-is, if that's ok.
} | ||
|
||
[Fact] | ||
public void ForeachIEnumerableDeclarationWithImplicitVarType() |
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 is more frequently ForEach
in the CSharp projects. Consider changing the new instances.
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.
Fixed the casing
LGTM |
@@ -321,6 +321,12 @@ public override AdjustSpacesOperation GetAdjustSpacesOperation(SyntaxToken previ | |||
return CreateAdjustSpacesOperation(0, AdjustSpacesOption.ForceSpaces); | |||
} | |||
|
|||
// Always put a space in the var form of deconstruction-declaration | |||
if (currentKind == SyntaxKind.OpenParenToken && currentToken.Parent.Kind() == SyntaxKind.VariableDeconstructionDeclarator && previousToken.Kind() == SyntaxKind.IdentifierToken && previousToken.ValueText == "var") |
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 slightly concerned that Parent
can be null here, if the formatting API is asked to format just a random stream of tokens.
@heejaechang does that sound possible to you? @jcouv if so, you might consider using currentToken.IsParentKind(SyntaxKind.VariableDeconstructionDeclarator)
instead.
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 don't think you need to check the valuetext of previous token. I would just check if the parent is a deconstruction, then see if the deconstruction has a non-null 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.
Thanks!
I didn't find an IsParentKind
extension that takes a SyntaxToken
(they only take SyntaxNode
), so I inlined a null check instead.
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.
@CyrusNajmabadi I pushed a new commit which doesn't rely on the ValueText
of the previous token as you suggested. Also adding a test for nested formatting.
Adds support for
for (var (x, y) in ...) { ... }
.Overview of the design:
Add more information into the
BoundForEachStatement
describing the deconstruction step. That information is a bound deconstruction assignment (BoundDeconstructionAssignmentOperator
) with a placeholder for the expression to be deconstructed.The
ForEachLoopBinder
declares more locals from itsBuildLocals
method. Some of them may need inference, whichInferTypeOfVarVariable
inSourceLocalSymbol.cs
can handle.There are four cases for
foreach
: IEnumerable, SZ array, MD array and string. In each case, if theBoundForEachStatement
has a deconstruction step, the current value for the iteration variable is deconstructed to produce multiple locals.I ran into some issues with the
GetDeclaredSymbol
API on those deconstruction locals, because the syntax for the deconstruction variables fall into thePatternVariableBinder
but not theForEachLoopBinder
which builds those locals.I will chat with @gafter next week to confirm my approach was ok.
I also made some minor updates to the parts of syntax that are attached to deconstruction diagnostics, and cleaned up some PROTOTYPE markers.
@dotnet/roslyn-compiler for review.
Deconstruction work items: #11299