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

Binding for deconstruction-declaration in 'foreach' statement #12326

Merged
merged 11 commits into from
Jul 7, 2016

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Jul 2, 2016

Adds support for for (var (x, y) in ...) { ... }.

Overview of the design:

  1. Initial binding
    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 its BuildLocals method. Some of them may need inference, which InferTypeOfVarVariable in SourceLocalSymbol.cs can handle.
  2. Local rewriter
    There are four cases for foreach: IEnumerable, SZ array, MD array and string. In each case, if the BoundForEachStatement 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 the PatternVariableBinder but not the ForEachLoopBinder 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

@gafter
Copy link
Member

gafter commented Jul 2, 2016

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.

@jcouv
Copy link
Member Author

jcouv commented Jul 2, 2016

@gafter I didn't know about that behavior. Thanks for pointing that out.

@gafter
Copy link
Member

gafter commented Jul 2, 2016

I will chat with @gafter next week to confirm my approach was ok.

I will be working from home, but I'm happy to chat with you whenever.

@gafter
Copy link
Member

gafter commented Jul 2, 2016

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

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.

Copy link
Member Author

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).

@jcouv
Copy link
Member Author

jcouv commented Jul 3, 2016

The last commit fixes the circularity problem, using ImplicitlyTypedLocalBinder (the same way it's done elsewhere in SourceLocalSymbol code.

@jcouv
Copy link
Member Author

jcouv commented Jul 5, 2016

@CyrusNajmabadi @dotnet/roslyn-ide The last commit contains a fix for formatting deconstruction-declaration syntax, if you want to review.
The proper formatting is var (x, y) = ... (with space after "var") rather than var(x, y) = ... (without space). This is independent of settings.

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

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.

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, I'll add that. I did verify manually in debugger though.

@VSadov
Copy link
Member

VSadov commented Jul 5, 2016

LGTM

@jcouv
Copy link
Member Author

jcouv commented Jul 5, 2016

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

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.

Copy link
Member Author

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed the casing

@cston
Copy link
Member

cston commented Jul 6, 2016

LGTM

@jcouv
Copy link
Member Author

jcouv commented Jul 6, 2016

@Pilchie Could I get an IDE code review on this small commit above? 3f48d29
It fixes a spacing issue with formatting deconstruction declarations.

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

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.

Copy link
Member

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.

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!
I didn't find an IsParentKind extension that takes a SyntaxToken (they only take SyntaxNode), so I inlined a null check instead.

Copy link
Member Author

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.

@jcouv jcouv merged commit 269bde0 into dotnet:features/tuples Jul 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants