From 269bde0859d12b2d5dc8d98130029a499f67cd31 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 6 Jul 2016 17:11:50 -0700 Subject: [PATCH] Binding for deconstruction-declaration in 'foreach' statement (#12326) --- .../Portable/Binder/Binder_Deconstruct.cs | 77 +- .../Portable/Binder/Binder_Statements.cs | 10 +- .../Portable/Binder/BuckStopsHereBinder.cs | 6 + .../Portable/Binder/ForEachLoopBinder.cs | 120 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 14 +- .../CSharp/Portable/BoundTree/Statement.cs | 2 +- .../Compilation/MemberSemanticModel.cs | 5 + .../Portable/FlowAnalysis/DataFlowPass.cs | 5 +- .../FlowAnalysis/DataFlowsOutWalker.cs | 2 +- .../Portable/FlowAnalysis/ReadWriteWalker.cs | 2 +- .../FlowAnalysis/VariablesDeclaredWalker.cs | 2 +- .../LocalRewriter_ForEachStatement.cs | 155 ++- .../CSharp/Portable/PublicAPI.Unshipped.txt | 2 +- .../Symbols/Source/SourceLocalSymbol.cs | 25 +- .../Portable/Syntax/ForEachStatementSyntax.cs | 2 + .../Emit/CodeGen/CodeGenDeconstructTests.cs | 1093 +++++++++++++++-- .../Test/Semantic/Semantics/ForEachTests.cs | 38 +- .../Portable/Formatting/FormattingHelpers.cs | 18 + .../Formatting/Rules/SpacingFormattingRule.cs | 6 + .../CSharpTest/Formatting/FormattingTests.cs | 42 + 20 files changed, 1389 insertions(+), 237 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index bc9b70073786f..a1e88aa02da52 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -18,8 +18,8 @@ internal partial class Binder { private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax node, DiagnosticBag diagnostics) { - SeparatedSyntaxList arguments = ((TupleExpressionSyntax)node.Left).Arguments; - ArrayBuilder checkedVariables = BindDeconstructionAssignmentVariables(arguments, diagnostics); + var left = (TupleExpressionSyntax)node.Left; + ArrayBuilder checkedVariables = BindDeconstructionAssignmentVariables(left.Arguments, left, diagnostics); var result = BindDeconstructionAssignment(node, node.Right, checkedVariables, diagnostics); FreeDeconstructionVariables(checkedVariables); @@ -47,12 +47,13 @@ private static void FreeDeconstructionVariables(ArrayBuilder - private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(ExpressionSyntax node, ExpressionSyntax right, ArrayBuilder checkedVariables, DiagnosticBag diagnostics) + private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(CSharpSyntaxNode node, ExpressionSyntax right, ArrayBuilder checkedVariables, DiagnosticBag diagnostics, BoundDeconstructValuePlaceholder rhsPlaceholder = null) { TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); // receiver for first Deconstruct step - var boundRHS = BindValue(right, diagnostics, BindValueKind.RValue); + var boundRHS = rhsPlaceholder ?? BindValue(right, diagnostics, BindValueKind.RValue); + if ((object)boundRHS.Type == null) { if (boundRHS.Kind == BoundKind.TupleLiteral) @@ -64,7 +65,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre else { // expression without type such as `null` - Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node); + Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, right); FailRemainingInferences(checkedVariables, diagnostics); return new BoundDeconstructionAssignmentOperator( @@ -76,7 +77,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre var deconstructionSteps = ArrayBuilder.GetInstance(1); var assignmentSteps = ArrayBuilder.GetInstance(1); - bool hasErrors = !DeconstructIntoSteps(new BoundDeconstructValuePlaceholder(right, boundRHS.Type), node, diagnostics, checkedVariables, deconstructionSteps, assignmentSteps); + bool hasErrors = !DeconstructIntoSteps(new BoundDeconstructValuePlaceholder(boundRHS.Syntax, boundRHS.Type), node, diagnostics, checkedVariables, deconstructionSteps, assignmentSteps); var deconstructions = deconstructionSteps.ToImmutableAndFree(); var assignments = assignmentSteps.ToImmutableAndFree(); @@ -93,7 +94,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre /// private bool DeconstructIntoSteps( BoundDeconstructValuePlaceholder targetPlaceholder, - ExpressionSyntax syntax, + CSharpSyntaxNode syntax, DiagnosticBag diagnostics, ArrayBuilder variables, ArrayBuilder deconstructionSteps, @@ -130,7 +131,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre /// private static BoundDeconstructionDeconstructStep MakeTupleDeconstructStep( BoundDeconstructValuePlaceholder targetPlaceholder, - ExpressionSyntax syntax, + CSharpSyntaxNode syntax, DiagnosticBag diagnostics, ArrayBuilder variables, ArrayBuilder deconstructionSteps, @@ -147,7 +148,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre return null; } - return new BoundDeconstructionDeconstructStep(syntax, null, targetPlaceholder, tupleTypes.SelectAsArray((t, s) => new BoundDeconstructValuePlaceholder(s, t), syntax)); + return new BoundDeconstructionDeconstructStep(syntax, null, targetPlaceholder, tupleTypes.SelectAsArray((t, i, v) => new BoundDeconstructValuePlaceholder(v[i].Syntax, t), variables)); } /// @@ -156,7 +157,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre /// private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep( BoundDeconstructValuePlaceholder targetPlaceholder, - ExpressionSyntax syntax, + CSharpSyntaxNode syntax, DiagnosticBag diagnostics, ArrayBuilder variables, ArrayBuilder deconstructionSteps, @@ -176,7 +177,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(Expre } /// - /// Inform the variables about found types (whether one was found or not). + /// Inform the variables about found types. /// private static void SetInferredTypes(ArrayBuilder variables, ImmutableArray foundTypes) { @@ -223,17 +224,20 @@ private class DeconstructionVariable { public readonly BoundExpression Single; public readonly ArrayBuilder NestedVariables; + public readonly CSharpSyntaxNode Syntax; public DeconstructionVariable(BoundExpression variable) { Single = variable; NestedVariables = null; + Syntax = variable.Syntax; } - public DeconstructionVariable(ArrayBuilder variables) + public DeconstructionVariable(ArrayBuilder variables, CSharpSyntaxNode syntax) { Single = null; NestedVariables = variables; + Syntax = syntax; } public bool HasNestedVariables => NestedVariables != null; @@ -246,7 +250,7 @@ public DeconstructionVariable(ArrayBuilder variables) private bool DeconstructOrAssignOutputs( BoundDeconstructionDeconstructStep deconstructionStep, ArrayBuilder variables, - ExpressionSyntax syntax, + CSharpSyntaxNode syntax, DiagnosticBag diagnostics, ArrayBuilder deconstructionSteps, ArrayBuilder assignmentSteps) @@ -303,7 +307,7 @@ private static TypeSymbol MakeTupleTypeFromDeconstructionLHS(ArrayBuilder - private ArrayBuilder BindDeconstructionAssignmentVariables(SeparatedSyntaxList arguments, DiagnosticBag diagnostics) + private ArrayBuilder BindDeconstructionAssignmentVariables(SeparatedSyntaxList arguments, CSharpSyntaxNode syntax, DiagnosticBag diagnostics) { int numElements = arguments.Count; Debug.Assert(numElements >= 2); // this should not have parsed as a tuple. @@ -315,8 +319,8 @@ private ArrayBuilder BindDeconstructionAssignmentVariabl { if (argument.Expression.Kind() == SyntaxKind.TupleExpression) // nested tuple case { - var nestedArguments = ((TupleExpressionSyntax)argument.Expression).Arguments; - checkedVariablesBuilder.Add(new DeconstructionVariable(BindDeconstructionAssignmentVariables(nestedArguments, diagnostics))); + var nested = (TupleExpressionSyntax)argument.Expression; + checkedVariablesBuilder.Add(new DeconstructionVariable(BindDeconstructionAssignmentVariables(nested.Arguments, nested, diagnostics), syntax)); } else { @@ -335,12 +339,12 @@ private ArrayBuilder BindDeconstructionAssignmentVariabl /// private BoundDeconstructionAssignmentStep MakeDeconstructionAssignmentStep( BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder, - ExpressionSyntax node, DiagnosticBag diagnostics) + CSharpSyntaxNode node, DiagnosticBag diagnostics) { var outputPlaceholder = new BoundDeconstructValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true }; // each assignment has a placeholder for a receiver and another for the source - BoundAssignmentOperator op = BindAssignment(node, outputPlaceholder, inputPlaceholder, diagnostics); + BoundAssignmentOperator op = BindAssignment(receivingVariable.Syntax, outputPlaceholder, inputPlaceholder, diagnostics); return new BoundDeconstructionAssignmentStep(node, op, inputPlaceholder, outputPlaceholder); } @@ -375,7 +379,7 @@ private static void FlattenDeconstructVariables(ArrayBuilder private BoundExpression MakeDeconstructInvocationExpression( - int numCheckedVariables, BoundExpression receiver, ExpressionSyntax assignmentSyntax, + int numCheckedVariables, BoundExpression receiver, CSharpSyntaxNode syntax, DiagnosticBag diagnostics, out ImmutableArray outPlaceholders) { var receiverSyntax = receiver.Syntax; @@ -396,7 +400,7 @@ private static void FlattenDeconstructVariables(ArrayBuilder (object)v.Placeholder == null)) { - return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); + return MissingDeconstruct(receiver, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result); } outPlaceholders = outVars.SelectAsArray(v => v.Placeholder); @@ -471,7 +475,7 @@ private static void FlattenDeconstructVariables(ArrayBuilder outPlaceholders, BoundNode childNode) + private BoundBadExpression MissingDeconstruct(BoundExpression receiver, CSharpSyntaxNode syntax, int numParameters, DiagnosticBag diagnostics, out ImmutableArray outPlaceholders, BoundNode childNode) { Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, receiver.Syntax, receiver.Type, numParameters); outPlaceholders = default(ImmutableArray); @@ -484,10 +488,16 @@ internal BoundLocalDeconstructionDeclaration BindDeconstructionDeclaration(CShar Debug.Assert(node.Kind() == SyntaxKind.LocalDeclarationStatement || node.Kind() == SyntaxKind.VariableDeclaration); Debug.Assert(declaration.IsDeconstructionDeclaration); - ArrayBuilder variables = BindDeconstructionDeclarationVariables(declaration, declaration.Type, diagnostics); + var value = declaration.Deconstruction.Value; + return new BoundLocalDeconstructionDeclaration(node, BindDeconstructionDeclaration(node, declaration, value, diagnostics)); + } - var result = new BoundLocalDeconstructionDeclaration(node, BindDeconstructionAssignment(declaration.Deconstruction.Value, declaration.Deconstruction.Value, variables, diagnostics)); - FreeDeconstructionVariables(variables); + internal BoundDeconstructionAssignmentOperator BindDeconstructionDeclaration(CSharpSyntaxNode node, VariableDeclarationSyntax declaration, ExpressionSyntax right, DiagnosticBag diagnostics, BoundDeconstructValuePlaceholder rightPlaceholder = null) + { + ArrayBuilder locals = BindDeconstructionDeclarationLocals(declaration, declaration.Type, diagnostics); + + var result = BindDeconstructionAssignment(node, right, locals, diagnostics, rightPlaceholder); + FreeDeconstructionVariables(locals); return result; } @@ -498,7 +508,7 @@ internal BoundLocalDeconstructionDeclaration BindDeconstructionDeclaration(CShar /// Each local is either a simple local (when its type is known) or a deconstruction local pending inference. /// The caller is responsible for releasing the nested ArrayBuilders. /// - private ArrayBuilder BindDeconstructionDeclarationVariables(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics) + private ArrayBuilder BindDeconstructionDeclarationLocals(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics) { Debug.Assert(node.IsDeconstructionDeclaration); SeparatedSyntaxList variables = node.Deconstruction.Variables; @@ -517,11 +527,11 @@ private ArrayBuilder BindDeconstructionDeclarationVariab DeconstructionVariable local; if (variable.IsDeconstructionDeclaration) { - local = new DeconstructionVariable(BindDeconstructionDeclarationVariables(variable, typeSyntax, diagnostics)); + local = new DeconstructionVariable(BindDeconstructionDeclarationLocals(variable, typeSyntax, diagnostics), node.Deconstruction); } else { - local = new DeconstructionVariable(BindDeconstructionDeclarationVariable(variable, typeSyntax, diagnostics)); + local = new DeconstructionVariable(BindDeconstructionDeclarationLocal(variable, typeSyntax, diagnostics)); } localsBuilder.Add(local); @@ -533,7 +543,7 @@ private ArrayBuilder BindDeconstructionDeclarationVariab /// /// Returns a BoundLocal when the type was explicit, otherwise returns a DeconstructionLocalPendingInference. /// - private BoundExpression BindDeconstructionDeclarationVariable(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics) + private BoundExpression BindDeconstructionDeclarationLocal(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics) { Debug.Assert(!node.IsDeconstructionDeclaration); Debug.Assert(node.Variables.Count == 1); @@ -567,4 +577,3 @@ private BoundExpression BindDeconstructionDeclarationVariable(VariableDeclaratio } } } - diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 983d561dbcf85..5d44e74a79fb3 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1723,7 +1723,7 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, Diagnost return BindAssignment(node, op1, op2, diagnostics); } - private BoundAssignmentOperator BindAssignment(ExpressionSyntax node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics) + private BoundAssignmentOperator BindAssignment(CSharpSyntaxNode node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics) { Debug.Assert(op1 != null); Debug.Assert(op2 != null); @@ -3086,6 +3086,14 @@ internal virtual BoundStatement BindForEachParts(DiagnosticBag diagnostics, Bind return this.Next.BindForEachParts(diagnostics, originalBinder); } + /// + /// Like BindForEachParts, but only bind the deconstruction part of the foreach, for purpose of inferring the types of the declared locals. + /// + internal virtual void BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder) + { + this.Next.BindForEachDeconstruction(diagnostics, originalBinder); + } + private BoundStatement BindBreak(BreakStatementSyntax node, DiagnosticBag diagnostics) { var target = this.BreakLabel; diff --git a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs index 48235005bdd7f..dfcccc93b1216 100644 --- a/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs @@ -182,6 +182,12 @@ internal override BoundStatement BindForEachParts(DiagnosticBag diagnostics, Bin throw ExceptionUtilities.Unreachable; } + internal override void BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder) + { + // There's supposed to be a ForEachLoopBinder (or other overrider of this method) in the chain. + throw ExceptionUtilities.Unreachable; + } + internal override BoundWhileStatement BindWhileParts(DiagnosticBag diagnostics, Binder originalBinder) { // There's supposed to be a WhileBinder (or other overrider of this method) in the chain. diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 35cdd5aaf0975..29c7bbb690975 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -27,7 +27,7 @@ private SourceLocalSymbol IterationVariable { get { - return (SourceLocalSymbol)this.Locals[0]; + return _syntax.IsDeconstructionDeclaration ? null : (SourceLocalSymbol)this.Locals[0]; } } @@ -40,14 +40,29 @@ public ForEachLoopBinder(Binder enclosing, ForEachStatementSyntax syntax) protected override ImmutableArray BuildLocals() { - var iterationVariable = SourceLocalSymbol.MakeForeachLocal( - (MethodSymbol)this.ContainingMemberOrLambda, - this, - _syntax.Type, - _syntax.Identifier, - _syntax.Expression); - - return ImmutableArray.Create(iterationVariable); + if (_syntax.IsDeconstructionDeclaration) + { + var locals = ArrayBuilder.GetInstance(); + + CollectLocalsFromDeconstruction( + _syntax.DeconstructionVariables, + _syntax.DeconstructionVariables.Type, + LocalDeclarationKind.ForEachIterationVariable, + locals); + + return locals.ToImmutableAndFree(); + } + else + { + var iterationVariable = SourceLocalSymbol.MakeForeachLocal( + (MethodSymbol)this.ContainingMemberOrLambda, + this, + _syntax.Type, + _syntax.Identifier, + _syntax.Expression); + + return ImmutableArray.Create(iterationVariable); + } } /// @@ -59,10 +74,32 @@ internal override BoundStatement BindForEachParts(DiagnosticBag diagnostics, Bin return result; } + /// + /// Like BindForEachParts, but only bind the deconstruction part of the foreach, for purpose of inferring the types of the declared locals. + /// + internal override void BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder) + { + // Use the right binder to avoid seeing iteration variable + BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); + + ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); + TypeSymbol inferredType; + bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); + + VariableDeclarationSyntax variables = _syntax.DeconstructionVariables; + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, inferredType ?? CreateErrorType("var")); + BoundDeconstructionAssignmentOperator deconstruction = BindDeconstructionDeclaration( + variables, + variables, + right: null, + diagnostics: diagnostics, + rightPlaceholder: valuePlaceholder); + } + private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, Binder originalBinder) { // Use the right binder to avoid seeing iteration variable - BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); + BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindValue(_syntax.Expression, diagnostics, BindValueKind.RValue); ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); TypeSymbol inferredType; @@ -74,33 +111,54 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, (object)builder.MoveNextMethod == null || (object)builder.CurrentPropertyGetter == null; - // Check for local variable conflicts in the *enclosing* binder; obviously the *current* - // binder has a local that matches! - var hasNameConflicts = originalBinder.ValidateDeclarationNameConflictsInScope(IterationVariable, diagnostics); - - // If the type in syntax is "var", then the type should be set explicitly so that the - // Type property doesn't fail. - - TypeSyntax typeSyntax = _syntax.Type; - - bool isVar; - AliasSymbol alias; - TypeSymbol declType = BindType(typeSyntax, diagnostics, out isVar, out alias); - TypeSymbol iterationVariableType; - if (isVar) + BoundTypeExpression boundIterationVariableType; + bool hasNameConflicts = false; + BoundForEachDeconstructStep deconstructStep = null; + if (_syntax.IsDeconstructionDeclaration) { iterationVariableType = inferredType ?? CreateErrorType("var"); + + VariableDeclarationSyntax variables = _syntax.DeconstructionVariables; + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, iterationVariableType); + BoundDeconstructionAssignmentOperator deconstruction = BindDeconstructionDeclaration( + variables, + variables, + right: null, + diagnostics: diagnostics, + rightPlaceholder: valuePlaceholder); + + deconstructStep = new BoundForEachDeconstructStep(_syntax.DeconstructionVariables, deconstruction, valuePlaceholder); + boundIterationVariableType = new BoundTypeExpression(variables, aliasOpt: null, type: iterationVariableType); } else { - Debug.Assert((object)declType != null); - iterationVariableType = declType; - } + // Check for local variable conflicts in the *enclosing* binder; obviously the *current* + // binder has a local that matches! + hasNameConflicts = originalBinder.ValidateDeclarationNameConflictsInScope(IterationVariable, diagnostics); + + // If the type in syntax is "var", then the type should be set explicitly so that the + // Type property doesn't fail. + TypeSyntax typeSyntax = _syntax.Type; - BoundTypeExpression boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); - this.IterationVariable.SetTypeSymbol(iterationVariableType); + bool isVar; + AliasSymbol alias; + TypeSymbol declType = BindType(typeSyntax, diagnostics, out isVar, out alias); + + if (isVar) + { + iterationVariableType = inferredType ?? CreateErrorType("var"); + } + else + { + Debug.Assert((object)declType != null); + iterationVariableType = declType; + } + + boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); + this.IterationVariable.SetTypeSymbol(iterationVariableType); + } BoundStatement body = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics); @@ -116,6 +174,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, boundIterationVariableType, this.IterationVariable, collectionExpr, + deconstructStep, body, CheckOverflowAtRuntime, this.BreakLabel, @@ -207,6 +266,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, boundIterationVariableType, this.IterationVariable, convertedCollectionExpression, + deconstructStep, body, CheckOverflowAtRuntime, this.BreakLabel, @@ -475,7 +535,7 @@ private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumerator } else { - builder.ElementType = collectionExprType.SpecialType == SpecialType.System_String? + builder.ElementType = collectionExprType.SpecialType == SpecialType.System_String ? GetSpecialType(SpecialType.System_Char, diagnostics, _syntax) : ((ArrayTypeSymbol)collectionExprType).ElementType; } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 5a1cea4dddbbc..cde087993656c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -816,19 +816,27 @@ - - - + + + + + + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Statement.cs b/src/Compilers/CSharp/Portable/BoundTree/Statement.cs index 7181963aef9f0..96b7f732706ec 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Statement.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Statement.cs @@ -262,7 +262,7 @@ public override void Accept(OperationVisitor visitor) internal partial class BoundForEachStatement : IForEachLoopStatement { - ILocalSymbol IForEachLoopStatement.IterationVariable => this.IterationVariable; + ILocalSymbol IForEachLoopStatement.IterationVariable => this.IterationVariableOpt; IOperation IForEachLoopStatement.Collection => this.Expression; diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 15834d5391dd1..46e2bcdd5f208 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -251,6 +251,11 @@ private static Binder GetEnclosingBinder(CSharpSyntaxNode node, int position, Bi { binder = rootBinder.GetBinder(current); } + else if (current.Kind() == SyntaxKind.VariableDeclaration && + (current.Parent as ForEachStatementSyntax)?.DeconstructionVariables == current) + { + binder = rootBinder.GetBinder(current.Parent); + } else { // If this ever breaks, make sure that all callers of diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index 10d21b6411a95..051d5a88b6758 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -1075,7 +1075,7 @@ protected virtual void AssignImpl(BoundNode node, BoundExpression value, RefKind case BoundKind.ForEachStatement: { - var iterationVariable = ((BoundForEachStatement)node).IterationVariable; + var iterationVariable = ((BoundForEachStatement)node).IterationVariableOpt; Debug.Assert((object)iterationVariable != null); int slot = GetOrCreateSlot(iterationVariable); if (slot > 0) SetSlotState(slot, written); @@ -1728,7 +1728,6 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct foreach (BoundExpression variable in node.LeftVariables) { - // PROTOTYPE(tuples) value should not be set to null Assign(variable, value: null, refKind: RefKind.None); } @@ -2055,7 +2054,7 @@ public override BoundNode VisitEventAccess(BoundEventAccess node) public override void VisitForEachIterationVariable(BoundForEachStatement node) { - var local = node.IterationVariable; + var local = node.IterationVariableOpt; if ((object)local != null) { GetOrCreateSlot(local); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowsOutWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowsOutWalker.cs index 088afd8af04b2..434007d83d668 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowsOutWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowsOutWalker.cs @@ -149,7 +149,7 @@ private Symbol GetNodeSymbol(BoundNode node) case BoundKind.ForEachStatement: { - return ((BoundForEachStatement)node).IterationVariable; + return ((BoundForEachStatement)node).IterationVariableOpt; } case BoundKind.RangeVariable: diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ReadWriteWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ReadWriteWalker.cs index 21b5bd223fd62..166692721e1d2 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ReadWriteWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ReadWriteWalker.cs @@ -215,7 +215,7 @@ public override BoundNode VisitUnboundLambda(UnboundLambda node) public override void VisitForEachIterationVariable(BoundForEachStatement node) { - var local = node.IterationVariable; + var local = node.IterationVariableOpt; if ((object)local != null) { GetOrCreateSlot(local); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs index 64c82e3db5a31..ed54a896e2621 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs @@ -103,7 +103,7 @@ public override BoundNode VisitForEachStatement(BoundForEachStatement node) { if (IsInside) { - _variablesDeclared.Add(node.IterationVariable); + _variablesDeclared.Add(node.IterationVariableOpt); } return base.VisitForEachStatement(node); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs index 29ff34b013c04..80ace542426e3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -using System.Collections.Generic; namespace Microsoft.CodeAnalysis.CSharp { @@ -57,11 +57,11 @@ public override BoundNode VisitForEachStatement(BoundForEachStatement node) /// /// Lower a foreach loop that will enumerate a collection using an enumerator. - /// + /// /// E e = ((C)(x)).GetEnumerator() /// try { /// while (e.MoveNext()) { - /// V v = (V)(T)e.Current; + /// V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; /// // body /// } /// } @@ -98,7 +98,7 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n AddForEachExpressionSequencePoint(forEachSyntax, ref enumeratorVarDecl); // V v - LocalSymbol iterationVar = node.IterationVariable; + LocalSymbol iterationVar = node.IterationVariableOpt; //(V)(T)e.Current BoundExpression iterationVarAssignValue = MakeConversion( @@ -113,20 +113,22 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n rewrittenType: elementType, @checked: node.Checked), conversion: node.ElementConversion, - rewrittenType: iterationVar.Type, + rewrittenType: node.IterationVariableType.Type, @checked: node.Checked); - // V v = (V)(T)e.Current; - BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarAssignValue); + // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; + + ImmutableArray iterationVariables; + BoundStatement iterationVarDecl = LocalOrDeconstructionDeclaration(node, iterationVar, iterationVarAssignValue, out iterationVariables); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); // while (e.MoveNext()) { - // V v = (V)(T)e.Current; + // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; // /* node.Body */ // } - var rewrittenBodyBlock = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); + var rewrittenBodyBlock = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVarDecl, rewrittenBody, forEachSyntax); BoundStatement whileLoop = RewriteWhileStatement( syntax: forEachSyntax, @@ -266,7 +268,7 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n // try { // while (e.MoveNext()) { - // V v = (V)(T)e.Current; + // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; // /* loop body */ // } // } @@ -294,7 +296,7 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n { // E e = ((C)(x)).GetEnumerator(); // while (e.MoveNext()) { - // V v = (V)(T)e.Current; + // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; // /* loop body */ // } result = new BoundBlock( @@ -368,10 +370,10 @@ private BoundExpression SynthesizeCall(CSharpSyntaxNode syntax, BoundExpression /// /// Lower a foreach loop that will enumerate the characters of a string. - /// + /// /// string s = x; /// for (int p = 0; p < s.Length; p = p + 1) { - /// V v = (V)s.Chars[p]; + /// V v = (V)s.Chars[p]; /* OR */ (D1 d1, ...) = (V)s.Chars[p]; /// // body /// } /// @@ -441,8 +443,7 @@ private BoundStatement RewriteStringForEachStatement(BoundForEachStatement node) // p = p + 1; BoundStatement positionIncrement = MakePositionIncrement(forEachSyntax, boundPositionVar, intType); - LocalSymbol iterationVar = node.IterationVariable; - TypeSymbol iterationVarType = iterationVar.Type; + LocalSymbol iterationVar = node.IterationVariableOpt; Debug.Assert(node.ElementConversion.IsValid); // (V)s.Chars[p] @@ -455,20 +456,24 @@ private BoundStatement RewriteStringForEachStatement(BoundForEachStatement node) method: chars, arguments: ImmutableArray.Create(boundPositionVar)), conversion: node.ElementConversion, - rewrittenType: iterationVarType, + rewrittenType: node.IterationVariableType.Type, @checked: node.Checked); - // V v = (V)s.Chars[p]; - BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarInitValue); + // V v = (V)s.Chars[p]; /* OR */ (D1 d1, ...) = (V)s.Chars[p]; + ImmutableArray iterationVariables; + BoundStatement iterationVarDecl = LocalOrDeconstructionDeclaration(node, iterationVar, iterationVarInitValue, out iterationVariables); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); - // { V v = (V)s.Chars[p]; /*node.Body*/ } + // { + // V v = (V)s.Chars[p]; /* OR */ (D1 d1, ...) = (V)s.Chars[p]; + // /* node.Body */ + // } - BoundStatement loopBody = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); + BoundStatement loopBody = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVarDecl, rewrittenBody, forEachSyntax); // for (string s = /*node.Expression*/, int p = 0; p < s.Length; p = p + 1) { - // V v = (V)s.Chars[p]; + // V v = (V)s.Chars[p]; /* OR */ (D1 d1, ...) = (V)s.Chars[p]; // /*node.Body*/ // } BoundStatement result = RewriteForStatement( @@ -489,8 +494,48 @@ private BoundStatement RewriteStringForEachStatement(BoundForEachStatement node) return result; } - private static BoundBlock CreateBlockDeclaringIterationVariable( - LocalSymbol iterationVariable, + /// + /// Takes the expression for the current value of the iteration variable and either + /// (1) assigns it into a local, or + /// (2) deconstructs it into multiple locals (if there is a deconstruct step). + /// + /// Produces `V v = /* expression */` or `(D1 d1, ...) = /* expression */`. + /// + private BoundStatement LocalOrDeconstructionDeclaration( + BoundForEachStatement forEachBound, + LocalSymbol iterationVar, + BoundExpression iterationVarValue, + out ImmutableArray iterationVariables) + { + var forEachSyntax = (ForEachStatementSyntax)forEachBound.Syntax; + + BoundStatement iterationVarDecl; + BoundForEachDeconstructStep deconstruction = forEachBound.DeconstructionOpt; + + if (deconstruction == null) + { + // V v = /* expression */ + iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarValue); + iterationVariables = ImmutableArray.Create(iterationVar); + } + else + { + // (D1 d1, ...) = /* expression */ + var assignment = deconstruction.DeconstructionAssignment; + + AddPlaceholderReplacement(deconstruction.TargetPlaceholder, iterationVarValue); + BoundExpression loweredAssignment = VisitExpression(assignment); + iterationVarDecl = new BoundExpressionStatement(assignment.Syntax, loweredAssignment); + RemovePlaceholderReplacement(deconstruction.TargetPlaceholder); + + iterationVariables = assignment.LeftVariables.SelectAsArray(v => ((BoundLocal)v).LocalSymbol); + } + + return iterationVarDecl; + } + + private static BoundBlock CreateBlockDeclaringIterationVariables( + ImmutableArray iterationVariables, BoundStatement iteratorVariableInitialization, BoundStatement rewrittenBody, ForEachStatementSyntax forEachSyntax) @@ -502,11 +547,11 @@ private BoundStatement RewriteStringForEachStatement(BoundForEachStatement node) // // We currently generate 2 closures. One containing variable x, the other variable y. // The EnC source mapping infrastructure requires each closure within a method body - // to have a unique syntax offset. Hence we associate the bound block declaring the + // to have a unique syntax offset. Hence we associate the bound block declaring the // iteration variable with the foreach statement, not the embedded statement. return new BoundBlock( forEachSyntax, - locals: ImmutableArray.Create(iterationVariable), + locals: iterationVariables, localFunctions: ImmutableArray.Empty, statements: ImmutableArray.Create(iteratorVariableInitialization, rewrittenBody)); } @@ -516,7 +561,7 @@ private BoundStatement RewriteStringForEachStatement(BoundForEachStatement node) /// /// A[] a = x; /// for (int p = 0; p < a.Length; p = p + 1) { - /// V v = (V)a[p]; + /// V v = (V)a[p]; /* OR */ (D1 d1, ...) = (V)a[p]; /// // body /// } /// @@ -566,8 +611,7 @@ private BoundStatement RewriteSingleDimensionalArrayForEachStatement(BoundForEac MakeLiteral(forEachSyntax, ConstantValue.Default(SpecialType.System_Int32), intType)); // V v - LocalSymbol iterationVar = node.IterationVariable; - TypeSymbol iterationVarType = iterationVar.Type; + LocalSymbol iterationVar = node.IterationVariableOpt; // (V)a[p] BoundExpression iterationVarInitValue = MakeConversion( @@ -578,11 +622,12 @@ private BoundStatement RewriteSingleDimensionalArrayForEachStatement(BoundForEac indices: ImmutableArray.Create(boundPositionVar), type: arrayType.ElementType), conversion: node.ElementConversion, - rewrittenType: iterationVarType, + rewrittenType: node.IterationVariableType.Type, @checked: node.Checked); - // V v = (V)a[p]; - BoundStatement iterationVariableDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarInitValue); + // V v = (V)a[p]; /* OR */ (D1 d1, ...) = (V)a[p]; + ImmutableArray iterationVariables; + BoundStatement iterationVariableDecl = LocalOrDeconstructionDeclaration(node, iterationVar, iterationVarInitValue, out iterationVariables); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVariableDecl); @@ -609,12 +654,15 @@ private BoundStatement RewriteSingleDimensionalArrayForEachStatement(BoundForEac // p = p + 1; BoundStatement positionIncrement = MakePositionIncrement(forEachSyntax, boundPositionVar, intType); - // { V v = (V)a[p]; /* node.Body */ } + // { + // V v = (V)a[p]; /* OR */ (D1 d1, ...) = (V)a[p]; + // /*node.Body*/ + // } - BoundStatement loopBody = CreateBlockDeclaringIterationVariable(iterationVar, iterationVariableDecl, rewrittenBody, forEachSyntax); + BoundStatement loopBody = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVariableDecl, rewrittenBody, forEachSyntax); // for (A[] a = /*node.Expression*/, int p = 0; p < a.Length; p = p + 1) { - // V v = (V)a[p]; + // V v = (V)a[p]; /* OR */ (D1 d1, ...) = (V)a[p]; // /*node.Body*/ // } BoundStatement result = RewriteForStatement( @@ -643,7 +691,10 @@ private BoundStatement RewriteSingleDimensionalArrayForEachStatement(BoundForEac /// for (int p_0 = a.GetLowerBound(0); p_0 <= q_0; p_0 = p_0 + 1) /// for (int p_1 = a.GetLowerBound(1); p_1 <= q_1; p_1 = p_1 + 1) /// ... - /// { V v = (V)a[p_0, p_1, ...]; /* body */ } + /// { + /// V v = (V)a[p_0, p_1, ...]; /* OR */ (D1 d1, ...) = (V)a[p_0, p_1, ...]; + /// /* body */ + /// } /// /// /// We will follow Dev10 in diverging from the C# 4 spec by ignoring Array's @@ -720,8 +771,7 @@ private BoundStatement RewriteMultiDimensionalArrayForEachStatement(BoundForEach } // V v - LocalSymbol iterationVar = node.IterationVariable; - TypeSymbol iterationVarType = iterationVar.Type; + LocalSymbol iterationVar = node.IterationVariableOpt; // (V)a[p_0, p_1, ...] BoundExpression iterationVarInitValue = MakeConversion( @@ -731,23 +781,31 @@ private BoundStatement RewriteMultiDimensionalArrayForEachStatement(BoundForEach indices: ImmutableArray.Create((BoundExpression[])boundPositionVar), type: arrayType.ElementType), conversion: node.ElementConversion, - rewrittenType: iterationVarType, + rewrittenType: node.IterationVariableType.Type, @checked: node.Checked); - // V v = (V)a[p_0, p_1, ...]; - BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarInitValue); + // V v = (V)a[p_0, p_1, ...]; /* OR */ (D1 d1, ...) = (V)a[p_0, p_1, ...]; + + ImmutableArray iterationVariables; + BoundStatement iterationVarDecl = LocalOrDeconstructionDeclaration(node, iterationVar, iterationVarInitValue, out iterationVariables); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); - // { V v = (V)a[p_0, p_1, ...]; /* node.Body */ } + // { + // V v = (V)a[p_0, p_1, ...]; /* OR */ (D1 d1, ...) = (V)a[p_0, p_1, ...]; + // /* node.Body */ + // } - BoundStatement innermostLoopBody = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); + BoundStatement innermostLoopBody = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVarDecl, rewrittenBody, forEachSyntax); // work from most-nested to least-nested // for (int p_0 = a.GetLowerBound(0); p_0 <= q_0; p_0 = p_0 + 1) // for (int p_1 = a.GetLowerBound(0); p_1 <= q_1; p_1 = p_1 + 1) // ... - // { V v = (V)a[p_0, p_1, ...]; /* node.Body */ } + // { + // V v = (V)a[p_0, p_1, ...]; /* OR */ (D1 d1, ...) = (V)a[p_0, p_1, ...]; + // /* body */ + // } BoundStatement forLoop = null; for (int dimension = rank - 1; dimension >= 0; dimension--) { @@ -912,7 +970,16 @@ private void AddForEachIterationVariableSequencePoint(ForEachStatementSyntax for { if (this.GenerateDebugInfo) { - TextSpan iterationVarDeclSpan = TextSpan.FromBounds(forEachSyntax.Type.SpanStart, forEachSyntax.Identifier.Span.End); + TextSpan iterationVarDeclSpan; + if (forEachSyntax.IsDeconstructionDeclaration) + { + iterationVarDeclSpan = forEachSyntax.DeconstructionVariables.Span; + } + else + { + iterationVarDeclSpan = TextSpan.FromBounds(forEachSyntax.Type.SpanStart, forEachSyntax.Identifier.Span.End); + } + iterationVarDecl = new BoundSequencePointWithSpan(forEachSyntax, iterationVarDecl, iterationVarDeclSpan); } } diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index eb9bebd7f50e1..50655a1b3ba6a 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -254,4 +254,4 @@ virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleEle virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax node) -> TResult virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleType(Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax node) -> TResult virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitVariableDeconstructionDeclarator(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeconstructionDeclaratorSyntax node) -> TResult -virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax node) -> TResult \ No newline at end of file +virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax node) -> TResult diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index 981c358564d31..83821b541c841 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -347,7 +347,7 @@ public override ImmutableArray DeclaringSyntaxReferences break; case LocalDeclarationKind.ForEachIterationVariable: - Debug.Assert(node is ForEachStatementSyntax); + Debug.Assert(node is ForEachStatementSyntax || node is VariableDeclaratorSyntax); break; case LocalDeclarationKind.CatchVariable: @@ -617,8 +617,13 @@ private class PossiblyImplicitlyTypedDeconstructionLocalSymbol : SourceLocalSymb #if DEBUG SyntaxNode parent; Debug.Assert(SyntaxFacts.IsDeconstructionIdentifier(identifierToken, out parent)); + Debug.Assert(parent.Parent != null); - Debug.Assert(parent.Parent.Kind() == SyntaxKind.LocalDeclarationStatement || parent.Parent.Kind() == SyntaxKind.ForStatement); + + Debug.Assert( + parent.Parent.Kind() == SyntaxKind.LocalDeclarationStatement || + parent.Parent.Kind() == SyntaxKind.ForStatement || + parent.Parent.Kind() == SyntaxKind.ForEachStatement); #endif } @@ -630,19 +635,27 @@ protected override TypeSymbol InferTypeOfVarVariable(DiagnosticBag diagnostics) Debug.Assert(isDeconstruction); Debug.Assert(((VariableDeclarationSyntax)topLevelVariableDeclaration).IsDeconstructionDeclaration); - Debug.Assert(((VariableDeclarationSyntax)topLevelVariableDeclaration).Deconstruction.Value != null); var statement = topLevelVariableDeclaration.Parent; switch (statement.Kind()) { case SyntaxKind.LocalDeclarationStatement: - this.binder.BindLocalDeclarationStatement((LocalDeclarationStatementSyntax)statement, diagnostics); + var localDecl = (LocalDeclarationStatementSyntax)statement; + var localBinder = this.binder.GetBinder(localDecl); + var newLocalBinder = new ImplicitlyTypedLocalBinder(localBinder, this); + newLocalBinder.BindDeconstructionDeclaration(localDecl, localDecl.Declaration, diagnostics); break; case SyntaxKind.ForStatement: var forStatement = (ForStatementSyntax)statement; - var loopBinder = this.binder.GetBinder(forStatement); - loopBinder.BindDeconstructionDeclaration(forStatement.Declaration, forStatement.Declaration, diagnostics); + var forBinder = this.binder.GetBinder(forStatement); + var newForBinder = new ImplicitlyTypedLocalBinder(forBinder, this); + newForBinder.BindDeconstructionDeclaration(forStatement.Declaration, forStatement.Declaration, diagnostics); + break; + + case SyntaxKind.ForEachStatement: + var foreachBinder = this.binder.GetBinder((ForEachStatementSyntax)statement); + foreachBinder.BindForEachDeconstruction(diagnostics, foreachBinder); break; default: diff --git a/src/Compilers/CSharp/Portable/Syntax/ForEachStatementSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/ForEachStatementSyntax.cs index 5351f6f1735b7..558e2165b2e16 100644 --- a/src/Compilers/CSharp/Portable/Syntax/ForEachStatementSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/ForEachStatementSyntax.cs @@ -8,5 +8,7 @@ public ForEachStatementSyntax Update(SyntaxToken forEachKeyword, SyntaxToken ope { return Update(forEachKeyword, openParenToken, type, identifier, null, inKeyword, expression, closeParenToken, statement); } + + internal bool IsDeconstructionDeclaration => DeconstructionVariables != null; } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index e2b52717ec9c1..f2c3aaee9a80f 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -1278,9 +1278,9 @@ static void Main() var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithRefsFeature()); comp.VerifyDiagnostics( - // (7,9): error CS8210: Deconstruct assignment requires an expression with a type on the right-hand-side. + // (7,18): error CS8210: Deconstruct assignment requires an expression with a type on the right-hand-side. // (x, x) = null; - Diagnostic(ErrorCode.ERR_DeconstructRequiresExpression, "(x, x) = null").WithLocation(7, 9) + Diagnostic(ErrorCode.ERR_DeconstructRequiresExpression, "null").WithLocation(7, 18) ); } @@ -1534,7 +1534,7 @@ .maxstack 3 } [Fact] - public void TupleWithNoConversion() + public void AssigningTupleWithNoConversion() { string source = @" class C @@ -1550,12 +1550,12 @@ static void Main() "; var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); comp.VerifyDiagnostics( - // (9,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) + // (9,10): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = (1, 2)").WithArguments("int", "byte").WithLocation(9, 9), - // (9,9): error CS0029: Cannot implicitly convert type 'int' to 'string' + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("int", "byte").WithLocation(9, 10), + // (9,13): error CS0029: Cannot implicitly convert type 'int' to 'string' // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = (1, 2)").WithArguments("int", "string").WithLocation(9, 9) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("int", "string").WithLocation(9, 13) ); } @@ -1652,7 +1652,6 @@ .maxstack 2 "); } - [Fact] public void TupleWithUseSiteError() { @@ -2586,13 +2585,7 @@ static void Main() comp.VerifyDiagnostics( // (6,14): error CS8213: Deconstruction must contain at least two variables. // for ((var (x, y)) = Pair.Create(1, 2); ;) { } - Diagnostic(ErrorCode.ERR_DeconstructTooFewElements, "(var (x, y)) = Pair.Create(1, 2)").WithLocation(6, 14), - // (6,29): error CS7036: There is no argument given that corresponds to the required formal parameter 'item2' of 'Pair.Deconstruct(out int, out int)' - // for ((var (x, y)) = Pair.Create(1, 2); ;) { } - Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "Pair.Create(1, 2)").WithArguments("item2", "Pair.Deconstruct(out int, out int)").WithLocation(6, 29), - // (6,29): error CS8206: No Deconstruct instance or extension method was found for type 'Pair', with 1 out parameters. - // for ((var (x, y)) = Pair.Create(1, 2); ;) { } - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "Pair.Create(1, 2)").WithArguments("Pair", "1").WithLocation(6, 29) + Diagnostic(ErrorCode.ERR_DeconstructTooFewElements, "(var (x, y)) = Pair.Create(1, 2)").WithLocation(6, 14) ); } @@ -2606,13 +2599,12 @@ static void Main() { var (x1, x2) = Pair.Create(1, 2); (int x3, int x4) = Pair.Create(1, 2); - //foreach ((int x5, var (x6, x7)) in null) { } - //for ((int x8, var (x9, x10)) = null; ; ) { } + foreach ((int x5, var (x6, x7)) in new[] { Pair.Create(1, Pair.Create(2, 3)) }) { } + for ((int x8, var (x9, x10)) = Pair.Create(1, Pair.Create(2, 3)); ; ) { } } } " + commonSource; - // PROTOTYPE(tuples) uncomment above once binding is fixed var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular6); comp.VerifyDiagnostics( // (6,9): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7 or greater. @@ -2620,7 +2612,13 @@ static void Main() Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "var (x1, x2) = Pair.Create(1, 2)").WithArguments("tuples", "7").WithLocation(6, 9), // (7,9): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7 or greater. // (int x3, int x4) = Pair.Create(1, 2); - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "(int x3, int x4) = Pair.Create(1, 2)").WithArguments("tuples", "7").WithLocation(7, 9) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "(int x3, int x4) = Pair.Create(1, 2)").WithArguments("tuples", "7").WithLocation(7, 9), + // (8,18): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7 or greater. + // foreach ((int x5, var (x6, x7)) in new[] { Pair.Create(1, Pair.Create(2, 3)) }) { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "(int x5, var (x6, x7))").WithArguments("tuples", "7").WithLocation(8, 18), + // (9,14): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7 or greater. + // for ((int x8, var (x9, x10)) = Pair.Create(1, Pair.Create(2, 3)); ; ) { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "(int x8, var (x9, x10)) = Pair.Create(1, Pair.Create(2, 3))").WithArguments("tuples", "7").WithLocation(9, 14) ); } @@ -3027,6 +3025,11 @@ private static void VerifyModelForDeconstructionFor(SemanticModel model, Variabl VerifyModelForDeconstruction(model, decl, LocalDeclarationKind.ForInitializerVariable, references); } + private static void VerifyModelForDeconstructionForeach(SemanticModel model, VariableDeclarationSyntax decl, params IdentifierNameSyntax[] references) + { + VerifyModelForDeconstruction(model, decl, LocalDeclarationKind.ForEachIterationVariable, references); + } + private static void VerifyModelForDeconstruction(SemanticModel model, VariableDeclarationSyntax decl, LocalDeclarationKind kind, params IdentifierNameSyntax[] references) { var variableDeclaratorSyntax = decl.Variables.Single(); @@ -3150,12 +3153,12 @@ class var { } // (6,18): error CS8215: Deconstruction `var (...)` form disallows a specific type for 'var'. // var (x1, x2) = (1, 2); Diagnostic(ErrorCode.ERR_DeconstructionVarFormDisallowsSpecificType, "x2").WithLocation(6, 18), - // (6,24): error CS0029: Cannot implicitly convert type 'int' to 'var' + // (6,14): error CS0029: Cannot implicitly convert type 'int' to 'var' // var (x1, x2) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("int", "var").WithLocation(6, 24), - // (6,24): error CS0029: Cannot implicitly convert type 'int' to 'var' + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x1").WithArguments("int", "var").WithLocation(6, 14), + // (6,18): error CS0029: Cannot implicitly convert type 'int' to 'var' // var (x1, x2) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("int", "var").WithLocation(6, 24) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x2").WithArguments("int", "var").WithLocation(6, 18) ); } @@ -3206,7 +3209,7 @@ static void Main() Assert.Null(x3.Type); Assert.Null(x4.Type); - var x34Var = (VariableDeclarationSyntax) x3.Parent.Parent; + var x34Var = (VariableDeclarationSyntax)x3.Parent.Parent; Assert.Equal("var", x34Var.Type.ToString()); Assert.Null(model.GetSymbolInfo(x34Var.Type).Symbol); // The var in `var (x3, x4)` has no symbol }; @@ -3292,12 +3295,12 @@ class D // (7,18): error CS8215: Deconstruction `var (...)` form disallows a specific type for 'var'. // var (x3, x4) = (3, 4); Diagnostic(ErrorCode.ERR_DeconstructionVarFormDisallowsSpecificType, "x4").WithLocation(7, 18), - // (7,24): error CS0029: Cannot implicitly convert type 'int' to 'D' + // (7,14): error CS0029: Cannot implicitly convert type 'int' to 'D' // var (x3, x4) = (3, 4); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(3, 4)").WithArguments("int", "D").WithLocation(7, 24), - // (7,24): error CS0029: Cannot implicitly convert type 'int' to 'D' + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x3").WithArguments("int", "D").WithLocation(7, 14), + // (7,18): error CS0029: Cannot implicitly convert type 'int' to 'D' // var (x3, x4) = (3, 4); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(3, 4)").WithArguments("int", "D").WithLocation(7, 24) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x4").WithArguments("int", "D").WithLocation(7, 18) ); } @@ -3316,13 +3319,13 @@ static void Main() var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); comp.VerifyDiagnostics( - // (6,34): error CS8211: Cannot deconstruct a tuple of '3' elements into '2' variables. + // (6,9): error CS8211: Cannot deconstruct a tuple of '3' elements into '2' variables. // (var (x1, x2), var x3) = (1, 2, 3); - Diagnostic(ErrorCode.ERR_DeconstructWrongCardinality, "(1, 2, 3)").WithArguments("3", "2").WithLocation(6, 34) + Diagnostic(ErrorCode.ERR_DeconstructWrongCardinality, "(var (x1, x2), var x3) = (1, 2, 3);").WithArguments("3", "2").WithLocation(6, 9) ); } - [Fact(Skip = "PROTOTYPE(tuples)")] + [Fact] public void DeclarationWithCircularity1() { string source = @" @@ -3331,40 +3334,19 @@ class C static void Main() { var (x1, x2) = (1, x1); - System.Console.WriteLine(x1 + "" "" + x2); } } "; - Action validator = (ModuleSymbol module) => - { - var sourceModule = (SourceModuleSymbol)module; - var compilation = sourceModule.DeclaringCompilation; - var tree = compilation.SyntaxTrees.First(); - var model = compilation.GetSemanticModel(tree); - - var x1 = GetDeconstructionLocal(tree, "x1"); - var x1Ref = GetReferences(tree, "x1", 2); - VerifyModelForDeconstructionLocal(model, x1, x1Ref); - - var x2 = GetDeconstructionLocal(tree, "x2"); - var x2Ref = GetReference(tree, "x2"); - VerifyModelForDeconstructionLocal(model, x2, x2Ref); - - // extra checks on x1 - Assert.Equal(SymbolKind.NamedType, model.GetSymbolInfo(x1.Type).Symbol.Kind); - Assert.Equal("int", model.GetSymbolInfo(x1.Type).Symbol.ToDisplayString()); - - // extra checks on x2 - Assert.Equal(SymbolKind.NamedType, model.GetSymbolInfo(x2.Type).Symbol.Kind); - Assert.Equal("int", model.GetSymbolInfo(x2.Type).Symbol.ToDisplayString()); - }; - - var comp = CompileAndVerify(source, expectedOutput: "1 1", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); - comp.VerifyDiagnostics( ); + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,28): error CS0165: Use of unassigned local variable 'x1' + // var (x1, x2) = (1, x1); + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(6, 28) + ); } - [Fact(Skip = "PROTOTYPE(tuples)")] + [Fact] public void DeclarationWithCircularity2() { string source = @" @@ -3373,37 +3355,15 @@ class C static void Main() { var (x1, x2) = (x2, 2); - System.Console.WriteLine(x1 + "" "" + x2); } } "; - - Action validator = (ModuleSymbol module) => - { - var sourceModule = (SourceModuleSymbol)module; - var compilation = sourceModule.DeclaringCompilation; - var tree = compilation.SyntaxTrees.First(); - var model = compilation.GetSemanticModel(tree); - - var x1 = GetDeconstructionLocal(tree, "x1"); - var x1Ref = GetReference(tree, "x1"); - VerifyModelForDeconstructionLocal(model, x1, x1Ref); - - var x2 = GetDeconstructionLocal(tree, "x2"); - var x2Ref = GetReferences(tree, "x2", 2); - VerifyModelForDeconstructionLocal(model, x2, x2Ref); - - // extra checks on x1 - Assert.Equal(SymbolKind.NamedType, model.GetSymbolInfo(x1.Type).Symbol.Kind); - Assert.Equal("int", model.GetSymbolInfo(x1.Type).Symbol.ToDisplayString()); - - // extra checks on x2 - Assert.Equal(SymbolKind.NamedType, model.GetSymbolInfo(x2.Type).Symbol.Kind); - Assert.Equal("int", model.GetSymbolInfo(x2.Type).Symbol.ToDisplayString()); - }; - - var comp = CompileAndVerify(source, expectedOutput: "2 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); - comp.VerifyDiagnostics(); + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,25): error CS0165: Use of unassigned local variable 'x2' + // var (x1, x2) = (x2, 2); + Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(6, 25) + ); } [Fact, CompilerTrait(CompilerFeature.RefLocalsReturns)] @@ -3542,7 +3502,7 @@ static void Main() } [Fact] - public void ForDeclarationWithImplicitVarType() + public void ForWithImplicitVarType() { string source = @" class C @@ -3579,11 +3539,11 @@ static void Main() }; var comp = CompileAndVerify(source, expectedOutput: "1 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); - comp.VerifyDiagnostics( ); + comp.VerifyDiagnostics(); } [Fact] - public void ForDeclarationWithActualVarType() + public void ForWithActualVarType() { string source = @" class C @@ -3631,7 +3591,7 @@ class var } [Fact] - public void ForDeconstructionWithTypes() + public void ForWithTypes() { string source = @" class C @@ -3675,7 +3635,7 @@ static void Main() } [Fact] - public void ForDeconstructionWithExistingVariableName() + public void ForWithExistingVariableName() { string source = @" class C @@ -3696,5 +3656,954 @@ static void Main() ); } + [Fact] + public void ForWithCircularity1() + { + string source = @" +class C +{ + static void Main() + { + for (var (x1, x2) = (1, x1); ; ) { } + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,33): error CS0165: Use of unassigned local variable 'x1' + // for (var (x1, x2) = (1, x1); ; ) { } + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(6, 33) + ); + } + + [Fact] + public void ForWithCircularity2() + { + string source = @" +class C +{ + static void Main() + { + for (var (x1, x2) = (x2, 2); ; ) { } + } +} +"; + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,30): error CS0165: Use of unassigned local variable 'x2' + // for (var (x1, x2) = (x2, 2); ; ) { } + Diagnostic(ErrorCode.ERR_UseDefViolation, "x2").WithArguments("x2").WithLocation(6, 30) + ); + } + + [Fact] + public void ForEachIEnumerableDeclarationWithImplicitVarType() + { + string source = @" +using System.Collections.Generic; +class C +{ + static void Main() + { + foreach (var (x1, x2) in M()) + { + Print(x1, x2); + } + } + static IEnumerable<(int, int)> M() { yield return (1, 2); } + static void Print(object a, object b) { System.Console.WriteLine(a + "" "" + b); } +} +"; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + // extra check on var + var x12Var = (VariableDeclarationSyntax)x1.Parent.Parent; + Assert.Equal("var", x12Var.Type.ToString()); + Assert.Null(model.GetSymbolInfo(x12Var.Type).Symbol); // The var in `var (x1, x2)` has no symbol + }; + + var comp = CompileAndVerify(source, expectedOutput: "1 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); + comp.VerifyDiagnostics(); + + comp.VerifyIL("C.Main", +@"{ + // Code size 70 (0x46) + .maxstack 2 + .locals init (System.Collections.Generic.IEnumerator<(int, int)> V_0, + int V_1, //x1 + int V_2) //x2 + IL_0000: call ""System.Collections.Generic.IEnumerable<(int, int)> C.M()"" + IL_0005: callvirt ""System.Collections.Generic.IEnumerator<(int, int)> System.Collections.Generic.IEnumerable<(int, int)>.GetEnumerator()"" + IL_000a: stloc.0 + .try + { + IL_000b: br.s IL_0031 + IL_000d: ldloc.0 + IL_000e: callvirt ""(int, int) System.Collections.Generic.IEnumerator<(int, int)>.Current.get"" + IL_0013: dup + IL_0014: ldfld ""int System.ValueTuple.Item1"" + IL_0019: stloc.1 + IL_001a: ldfld ""int System.ValueTuple.Item2"" + IL_001f: stloc.2 + IL_0020: ldloc.1 + IL_0021: box ""int"" + IL_0026: ldloc.2 + IL_0027: box ""int"" + IL_002c: call ""void C.Print(object, object)"" + IL_0031: ldloc.0 + IL_0032: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_0037: brtrue.s IL_000d + IL_0039: leave.s IL_0045 + } + finally + { + IL_003b: ldloc.0 + IL_003c: brfalse.s IL_0044 + IL_003e: ldloc.0 + IL_003f: callvirt ""void System.IDisposable.Dispose()"" + IL_0044: endfinally + } + IL_0045: ret +} +"); + } + + [Fact] + public void ForEachSZArrayDeclarationWithImplicitVarType() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in M()) + { + System.Console.Write(x1 + "" "" + x2 + "" - ""); + } + } + static (int, int)[] M() { return new[] { (1, 2), (3, 4) }; } +} +"; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + var variableDeclaratorSyntax = x1.Variables.Single(); + var symbol = model.GetDeclaredSymbol(variableDeclaratorSyntax); + + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + // extra check on var + var x12Var = (VariableDeclarationSyntax)x1.Parent.Parent; + Assert.Equal("var", x12Var.Type.ToString()); + 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); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", +@"{ + // Code size 91 (0x5b) + .maxstack 4 + .locals init ((int, int)[] V_0, + int V_1, + int V_2, //x1 + int V_3) //x2 + IL_0000: call ""(int, int)[] C.M()"" + IL_0005: stloc.0 + IL_0006: ldc.i4.0 + IL_0007: stloc.1 + IL_0008: br.s IL_0054 + IL_000a: ldloc.0 + IL_000b: ldloc.1 + IL_000c: ldelem ""System.ValueTuple"" + IL_0011: dup + IL_0012: ldfld ""int System.ValueTuple.Item1"" + IL_0017: stloc.2 + IL_0018: ldfld ""int System.ValueTuple.Item2"" + IL_001d: stloc.3 + IL_001e: ldc.i4.4 + IL_001f: newarr ""object"" + IL_0024: dup + IL_0025: ldc.i4.0 + IL_0026: ldloc.2 + IL_0027: box ""int"" + IL_002c: stelem.ref + IL_002d: dup + IL_002e: ldc.i4.1 + IL_002f: ldstr "" "" + IL_0034: stelem.ref + IL_0035: dup + IL_0036: ldc.i4.2 + IL_0037: ldloc.3 + IL_0038: box ""int"" + IL_003d: stelem.ref + IL_003e: dup + IL_003f: ldc.i4.3 + IL_0040: ldstr "" - "" + IL_0045: stelem.ref + IL_0046: call ""string string.Concat(params object[])"" + IL_004b: call ""void System.Console.Write(string)"" + IL_0050: ldloc.1 + IL_0051: ldc.i4.1 + IL_0052: add + IL_0053: stloc.1 + IL_0054: ldloc.1 + IL_0055: ldloc.0 + IL_0056: ldlen + IL_0057: conv.i4 + IL_0058: blt.s IL_000a + IL_005a: ret +}"); + } + + [Fact] + public void ForEachMDArrayDeclarationWithImplicitVarType() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in M()) + { + Print(x1, x2); + } + } + static (int, int)[,] M() { return new (int, int)[2, 2] { { (1, 2), (3, 4) }, { (5, 6), (7, 8) } }; } + static void Print(object a, object b) { System.Console.Write(a + "" "" + b + "" - ""); } +} +"; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + // extra check on var + var x12Var = (VariableDeclarationSyntax)x1.Parent.Parent; + Assert.Equal("var", x12Var.Type.ToString()); + 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 - 5 6 - 7 8 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", +@"{ + // Code size 106 (0x6a) + .maxstack 3 + .locals init ((int, int)[,] V_0, + int V_1, + int V_2, + int V_3, + int V_4, + int V_5, //x1 + int V_6) //x2 + IL_0000: call ""(int, int)[,] C.M()"" + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: ldc.i4.0 + IL_0008: callvirt ""int System.Array.GetUpperBound(int)"" + IL_000d: stloc.1 + IL_000e: ldloc.0 + IL_000f: ldc.i4.1 + IL_0010: callvirt ""int System.Array.GetUpperBound(int)"" + IL_0015: stloc.2 + IL_0016: ldloc.0 + IL_0017: ldc.i4.0 + IL_0018: callvirt ""int System.Array.GetLowerBound(int)"" + IL_001d: stloc.3 + IL_001e: br.s IL_0065 + IL_0020: ldloc.0 + IL_0021: ldc.i4.1 + IL_0022: callvirt ""int System.Array.GetLowerBound(int)"" + IL_0027: stloc.s V_4 + IL_0029: br.s IL_005c + IL_002b: ldloc.0 + IL_002c: ldloc.3 + IL_002d: ldloc.s V_4 + IL_002f: call ""(int, int)[*,*].Get"" + IL_0034: dup + IL_0035: ldfld ""int System.ValueTuple.Item1"" + IL_003a: stloc.s V_5 + IL_003c: ldfld ""int System.ValueTuple.Item2"" + IL_0041: stloc.s V_6 + IL_0043: ldloc.s V_5 + IL_0045: box ""int"" + IL_004a: ldloc.s V_6 + IL_004c: box ""int"" + IL_0051: call ""void C.Print(object, object)"" + IL_0056: ldloc.s V_4 + IL_0058: ldc.i4.1 + IL_0059: add + IL_005a: stloc.s V_4 + IL_005c: ldloc.s V_4 + IL_005e: ldloc.2 + IL_005f: ble.s IL_002b + IL_0061: ldloc.3 + IL_0062: ldc.i4.1 + IL_0063: add + IL_0064: stloc.3 + IL_0065: ldloc.3 + IL_0066: ldloc.1 + IL_0067: ble.s IL_0020 + IL_0069: ret +}"); + } + + [Fact] + public void ForEachStringDeclarationWithImplicitVarType() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in M()) + { + Print(x1, x2); + } + } + static string M() { return ""123""; } + static void Print(object a, object b) { System.Console.Write(a + "" "" + b + "" - ""); } +} +static class Extension +{ + public static void Deconstruct(this char value, out int item1, out int item2) + { + item1 = item2 = System.Int32.Parse(value.ToString()); } } +"; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + // extra check on var + var x12Var = (VariableDeclarationSyntax)x1.Parent.Parent; + Assert.Equal("var", x12Var.Type.ToString()); + Assert.Null(model.GetSymbolInfo(x12Var.Type).Symbol); // The var in `var (x1, x2)` has no symbol + }; + + var comp = CompileAndVerify(source, expectedOutput: "1 1 - 2 2 - 3 3 - ", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef }, sourceSymbolValidator: validator); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", +@"{ + // Code size 60 (0x3c) + .maxstack 3 + .locals init (string V_0, + int V_1, + int V_2, //x2 + int V_3, + int V_4) + IL_0000: call ""string C.M()"" + IL_0005: stloc.0 + IL_0006: ldc.i4.0 + IL_0007: stloc.1 + IL_0008: br.s IL_0032 + IL_000a: ldloc.0 + IL_000b: ldloc.1 + IL_000c: callvirt ""char string.this[int].get"" + IL_0011: ldloca.s V_3 + IL_0013: ldloca.s V_4 + IL_0015: call ""void Extension.Deconstruct(char, out int, out int)"" + IL_001a: ldloc.3 + IL_001b: ldloc.s V_4 + IL_001d: stloc.2 + IL_001e: box ""int"" + IL_0023: ldloc.2 + IL_0024: box ""int"" + IL_0029: call ""void C.Print(object, object)"" + IL_002e: ldloc.1 + IL_002f: ldc.i4.1 + IL_0030: add + IL_0031: stloc.1 + IL_0032: ldloc.1 + IL_0033: ldloc.0 + IL_0034: callvirt ""int string.Length.get"" + IL_0039: blt.s IL_000a + IL_003b: ret +}"); + } + + [Fact] + public void ForEachIEnumerableDeclarationWithNesting() + { + string source = @" +using System.Collections.Generic; +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3), (int x4, int x5)) in M()) + { + System.Console.Write(x1 + "" "" + x2 + "" "" + x3 + "" "" + x4 + "" "" + x5 + "" - ""); + } + } + static IEnumerable<(int, (int, int), (int, int))> M() { yield return (1, (2, 3), (4, 5)); yield return (6, (7, 8), (9, 10)); } +} +"; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + var x3 = GetDeconstructionLocal(tree, "x3"); + var x3Ref = GetReference(tree, "x3"); + VerifyModelForDeconstructionForeach(model, x3, x3Ref); + + var x4 = GetDeconstructionLocal(tree, "x4"); + var x4Ref = GetReference(tree, "x4"); + VerifyModelForDeconstructionForeach(model, x4, x4Ref); + + var x5 = GetDeconstructionLocal(tree, "x5"); + var x5Ref = GetReference(tree, "x5"); + VerifyModelForDeconstructionForeach(model, x5, x5Ref); + + // extra check on var + var x23Var = (VariableDeclarationSyntax)x2.Parent.Parent; + Assert.Equal("var", x23Var.Type.ToString()); + Assert.Null(model.GetSymbolInfo(x23Var.Type).Symbol); // The var in `var (x2, x3)` has no symbol + }; + + var comp = CompileAndVerify(source, expectedOutput: "1 2 3 4 5 - 6 7 8 9 10 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachSZArrayDeclarationWithNesting() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3), (int x4, int x5)) in M()) + { + System.Console.Write(x1 + "" "" + x2 + "" "" + x3 + "" "" + x4 + "" "" + x5 + "" - ""); + } + } + static (int, (int, int), (int, int))[] M() { return new[] { (1, (2, 3), (4, 5)), (6, (7, 8), (9, 10)) }; } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 2 3 4 5 - 6 7 8 9 10 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachMDArrayDeclarationWithNesting() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3), (int x4, int x5)) in M()) + { + System.Console.Write(x1 + "" "" + x2 + "" "" + x3 + "" "" + x4 + "" "" + x5 + "" - ""); + } + } + static (int, (int, int), (int, int))[,] M() { return new(int, (int, int), (int, int))[1, 2] { { (1, (2, 3), (4, 5)), (6, (7, 8), (9, 10)) } }; } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 2 3 4 5 - 6 7 8 9 10 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachStringDeclarationWithNesting() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3)) in M()) + { + System.Console.Write(x1 + "" "" + x2 + "" "" + x3 + "" - ""); + } + } + static string M() { return ""12""; } +} +static class Extension +{ + public static void Deconstruct(this char value, out int item1, out (int, int) item2) + { + item1 = System.Int32.Parse(value.ToString()); + item2 = (item1, item1); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 1 1 - 2 2 2 - ", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachIEnumerableDeclarationWithDeconstruct() + { + string source = @" +using System.Collections.Generic; +class C +{ + static void Main() + { + foreach ((long x1, var (x2, x3)) in M()) + { + Print(x1, x2, x3); + } + } + static IEnumerable>> M() { yield return Pair.Create(1, Pair.Create(2, 3)); yield return Pair.Create(4, Pair.Create(5, 6)); } + static void Print(object a, object b, object c) { System.Console.WriteLine(a + "" "" + b + "" "" + c); } +} +" + commonSource; + + Action validator = (ModuleSymbol module) => + { + var sourceModule = (SourceModuleSymbol)module; + var compilation = sourceModule.DeclaringCompilation; + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x1 = GetDeconstructionLocal(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionForeach(model, x1, x1Ref); + + var x2 = GetDeconstructionLocal(tree, "x2"); + var x2Ref = GetReference(tree, "x2"); + VerifyModelForDeconstructionForeach(model, x2, x2Ref); + + var x3 = GetDeconstructionLocal(tree, "x3"); + var x3Ref = GetReference(tree, "x3"); + VerifyModelForDeconstructionForeach(model, x3, x3Ref); + + // extra check on var + var x23Var = (VariableDeclarationSyntax)x2.Parent.Parent; + Assert.Equal("var", x23Var.Type.ToString()); + Assert.Null(model.GetSymbolInfo(x23Var.Type).Symbol); // The var in `var (x2, x3)` has no symbol + }; + + string expected = +@"Deconstructing (1, (2, 3)) +Deconstructing (2, 3) +1 2 3 +Deconstructing (4, (5, 6)) +Deconstructing (5, 6) +4 5 6"; + + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, sourceSymbolValidator: validator); + comp.VerifyDiagnostics(); + + comp.VerifyIL("C.Main", +@"{ + // Code size 90 (0x5a) + .maxstack 3 + .locals init (System.Collections.Generic.IEnumerator>> V_0, + int V_1, //x2 + int V_2, //x3 + int V_3, + Pair V_4, + int V_5, + int V_6) + IL_0000: call ""System.Collections.Generic.IEnumerable>> C.M()"" + IL_0005: callvirt ""System.Collections.Generic.IEnumerator>> System.Collections.Generic.IEnumerable>>.GetEnumerator()"" + IL_000a: stloc.0 + .try + { + IL_000b: br.s IL_0045 + IL_000d: ldloc.0 + IL_000e: callvirt ""Pair> System.Collections.Generic.IEnumerator>>.Current.get"" + IL_0013: ldloca.s V_3 + IL_0015: ldloca.s V_4 + IL_0017: callvirt ""void Pair>.Deconstruct(out int, out Pair)"" + IL_001c: ldloc.s V_4 + IL_001e: ldloca.s V_5 + IL_0020: ldloca.s V_6 + IL_0022: callvirt ""void Pair.Deconstruct(out int, out int)"" + IL_0027: ldloc.3 + IL_0028: conv.i8 + IL_0029: ldloc.s V_5 + IL_002b: stloc.1 + IL_002c: ldloc.s V_6 + IL_002e: stloc.2 + IL_002f: box ""long"" + IL_0034: ldloc.1 + IL_0035: box ""int"" + IL_003a: ldloc.2 + IL_003b: box ""int"" + IL_0040: call ""void C.Print(object, object, object)"" + IL_0045: ldloc.0 + IL_0046: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_004b: brtrue.s IL_000d + IL_004d: leave.s IL_0059 + } + finally + { + IL_004f: ldloc.0 + IL_0050: brfalse.s IL_0058 + IL_0052: ldloc.0 + IL_0053: callvirt ""void System.IDisposable.Dispose()"" + IL_0058: endfinally + } + IL_0059: ret +} +"); + } + + [Fact] + public void ForEachSZArrayDeclarationWithDeconstruct() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3)) in M()) + { + System.Console.WriteLine(x1 + "" "" + x2 + "" "" + x3); + } + } + static Pair>[] M() { return new[] { Pair.Create(1, Pair.Create(2, 3)), Pair.Create(4, Pair.Create(5, 6)) }; } +} +" + commonSource; + + string expected = +@"Deconstructing (1, (2, 3)) +Deconstructing (2, 3) +1 2 3 +Deconstructing (4, (5, 6)) +Deconstructing (5, 6) +4 5 6"; + + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachMDArrayDeclarationWithDeconstruct() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3)) in M()) + { + System.Console.WriteLine(x1 + "" "" + x2 + "" "" + x3); + } + } + static Pair>[,] M() { return new Pair> [1, 2] { { Pair.Create(1, Pair.Create(2, 3)), Pair.Create(4, Pair.Create(5, 6)) } }; } +} +" + commonSource; + + string expected = +@"Deconstructing (1, (2, 3)) +Deconstructing (2, 3) +1 2 3 +Deconstructing (4, (5, 6)) +Deconstructing (5, 6) +4 5 6"; + + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachNameConflict() + { + string source = @" +class C +{ + static void Main() + { + int x1 = 1; + foreach ((int x1, int x2) in M()) { } + System.Console.Write(x1); + } + static (int, int)[] M() { return new[] { (1, 2) }; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (7,23): error CS0136: A local or parameter named 'x1' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter + // foreach ((int x1, int x2) in M()) + Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x1").WithArguments("x1").WithLocation(7, 23) + ); + } + + [Fact] + public void ForEachNameConflict2() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, int x2) in M(out int x1)) { } + } + static (int, int)[] M(out int a) { a = 1; return new[] { (1, 2) }; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,23): error CS0136: A local or parameter named 'x1' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter + // foreach ((int x1, int x2) in M(out int x1)) { } + Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x1").WithArguments("x1").WithLocation(6, 23) + ); + } + + [Fact] + public void ForEachNameConflict3() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, int x2) in M()) + { + int x1 = 1; + System.Console.Write(x1); + } + } + static (int, int)[] M() { return new[] { (1, 2) }; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (8,17): error CS0136: A local or parameter named 'x1' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter + // int x1 = 1; + Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x1").WithArguments("x1").WithLocation(8, 17) + ); + } + + [Fact] + public void ForEachUseBeforeDeclared() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, int x2) in M(x1)) { } + } + static (int, int)[] M(int a) { return new[] { (1, 2) }; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,40): error CS0103: The name 'x1' does not exist in the current context + // foreach ((int x1, int x2) in M(x1)) + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 40) + ); + } + + [Fact] + public void ForEachUseOutsideScope() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, int x2) in M()) { } + System.Console.Write(x1); + } + static (int, int)[] M() { return new[] { (1, 2) }; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (7,30): error CS0103: The name 'x1' does not exist in the current context + // System.Console.Write(x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 30) + ); + } + + [Fact] + public void ForEachNoIEnumerable() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in 1) + { + System.Console.WriteLine(x1 + "" "" + x2); + } + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,34): error CS1579: foreach statement cannot operate on variables of type 'int' because 'int' does not contain a public definition for 'GetEnumerator' + // foreach (var (x1, x2) in 1) + Diagnostic(ErrorCode.ERR_ForEachMissingMember, "1").WithArguments("int", "GetEnumerator").WithLocation(6, 34) + ); + } + + [Fact] + public void ForEachIterationVariablesAreReadonly() + { + string source = @" +class C +{ + static void Main() + { + foreach ((int x1, var (x2, x3)) in new[] { (1, (1, 1)) }) + { + x1 = 1; + x2 = 2; + x3 = 3; + } + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (8,13): error CS1656: Cannot assign to 'x1' because it is a 'foreach iteration variable' + // x1 = 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "x1").WithArguments("x1", "foreach iteration variable").WithLocation(8, 13), + // (9,13): error CS1656: Cannot assign to 'x2' because it is a 'foreach iteration variable' + // x2 = 2; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "x2").WithArguments("x2", "foreach iteration variable").WithLocation(9, 13), + // (10,13): error CS1656: Cannot assign to 'x3' because it is a 'foreach iteration variable' + // x3 = 3; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocalCause, "x3").WithArguments("x3", "foreach iteration variable").WithLocation(10, 13) + ); + } + + [Fact] + public void ForEachWithExpressionBody() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in new[] { (1, 2), (3, 4) }) + System.Console.Write(x1 + "" "" + x2 + "" - ""); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 2 - 3 4 -", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ForEachScoping() + { + string source = @" +class C +{ + static void Main() + { + foreach (var (x1, x2) in M(x1)) { } + } + static (int, int) M(int i) { return (1, 2); } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (6,36): error CS0103: The name 'x1' does not exist in the current context + // foreach (var (x1, x2) in M(x1)) { } + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 36) + ); + } + + [Fact] + public void ForEachCreatesNewVariables() + { + string source = @" +class C +{ + static void Main() + { + var lambdas = new System.Action[2]; + int index = 0; + foreach (var (x1, x2) in M()) + { + lambdas[index] = () => { System.Console.Write(x1 + "" ""); }; + index++; + } + lambdas[0](); + lambdas[1](); + } + static (int, int)[] M() { return new[] { (0, 0), (10, 10) }; } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "0 10 ", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics(); + } + } +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs index 75fe5bbd567d9..4a6a8de715083 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs @@ -1203,7 +1203,7 @@ void Foo(int[] a) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int32 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int32 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_Collections_IEnumerable, boundNode.Expression.Type.SpecialType); Assert.Equal(SymbolKind.ArrayType, ((BoundConversion)boundNode.Expression).Operand.Type.Kind); } @@ -1235,7 +1235,7 @@ void Foo(string s) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("System.Char c", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Char c", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_String, boundNode.Expression.Type.SpecialType); Assert.Equal(SpecialType.System_String, ((BoundConversion)boundNode.Expression).Operand.Type.SpecialType); } @@ -1278,7 +1278,7 @@ class Enumerator Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.ImplicitNumeric, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("Enumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1321,7 +1321,7 @@ struct Enumerator Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.ImplicitNumeric, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("Enumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1353,7 +1353,7 @@ void Foo(System.Collections.IEnumerable e) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Unboxing, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("System.Collections.IEnumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("System.Collections.IEnumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1392,7 +1392,7 @@ class Enumerable : System.Collections.Generic.IEnumerable Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.ImplicitNumeric, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("System.Collections.Generic.IEnumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1433,7 +1433,7 @@ private class Hidden { } Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("System.Object x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Object x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_Collections_IEnumerable, boundNode.Expression.Type.SpecialType); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1471,7 +1471,7 @@ class Enumerable : System.Collections.IEnumerable Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Unboxing, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_Collections_IEnumerable, boundNode.Expression.Type.SpecialType); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1503,7 +1503,7 @@ void Foo(int[] a) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal(SpecialType.System_Int32, boundNode.IterationVariable.Type.SpecialType); + Assert.Equal(SpecialType.System_Int32, boundNode.IterationVariableOpt.Type.SpecialType); } [Fact] @@ -1533,7 +1533,7 @@ void Foo(string s) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal(SpecialType.System_Char, boundNode.IterationVariable.Type.SpecialType); + Assert.Equal(SpecialType.System_Char, boundNode.IterationVariableOpt.Type.SpecialType); } [Fact] @@ -1562,7 +1562,7 @@ class Enumerator var boundNode = GetBoundForEachStatement(text); Assert.NotNull(boundNode.EnumeratorInfoOpt); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal(SpecialType.System_Int32, boundNode.IterationVariable.Type.SpecialType); + Assert.Equal(SpecialType.System_Int32, boundNode.IterationVariableOpt.Type.SpecialType); } [Fact] @@ -1586,7 +1586,7 @@ class Enumerable : System.Collections.IEnumerable var boundNode = GetBoundForEachStatement(text); Assert.NotNull(boundNode.EnumeratorInfoOpt); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal(SpecialType.System_Object, boundNode.IterationVariable.Type.SpecialType); + Assert.Equal(SpecialType.System_Object, boundNode.IterationVariableOpt.Type.SpecialType); } [Fact] @@ -1618,7 +1618,7 @@ class var { } Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("C.var", boundNode.IterationVariable.Type.ToTestDisplayString()); + Assert.Equal("C.var", boundNode.IterationVariableOpt.Type.ToTestDisplayString()); } [Fact] @@ -1648,7 +1648,7 @@ void Foo(dynamic d) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.ExplicitDynamic, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int32 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int32 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_Collections_IEnumerable, boundNode.Expression.Type.SpecialType); Assert.Equal(TypeKind.Dynamic, ((BoundConversion)boundNode.Expression).Operand.Type.TypeKind); } @@ -1680,7 +1680,7 @@ void Foo(dynamic d) Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); //NB: differs from explicit case - Assert.Equal("dynamic x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("dynamic x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal(SpecialType.System_Collections_IEnumerable, boundNode.Expression.Type.SpecialType); Assert.Equal(SymbolKind.DynamicType, ((BoundConversion)boundNode.Expression).Operand.Type.Kind); } @@ -1720,7 +1720,7 @@ public class Enumerable Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("System.Object x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Object x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("Enumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1803,7 +1803,7 @@ interface MyEnumerator Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal("System.Object x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Object x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("Enumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -1849,7 +1849,7 @@ struct Enumerator Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.ImplicitNumeric, boundNode.ElementConversion.Kind); - Assert.Equal("System.Int64 x", boundNode.IterationVariable.ToTestDisplayString()); + Assert.Equal("System.Int64 x", boundNode.IterationVariableOpt.ToTestDisplayString()); Assert.Equal("Enumerable", boundNode.Expression.Type.ToTestDisplayString()); Assert.Equal("Enumerable", ((BoundConversion)boundNode.Expression).Operand.Type.ToTestDisplayString()); } @@ -2991,7 +2991,7 @@ .maxstack 2 Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind); Assert.Equal(ConversionKind.Identity, boundNode.ElementConversion.Kind); - Assert.Equal(SpecialType.System_Char, boundNode.IterationVariable.Type.SpecialType); + Assert.Equal(SpecialType.System_Char, boundNode.IterationVariableOpt.Type.SpecialType); } diff --git a/src/Workspaces/CSharp/Portable/Formatting/FormattingHelpers.cs b/src/Workspaces/CSharp/Portable/Formatting/FormattingHelpers.cs index d95209a398c46..ab8e032ae536b 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/FormattingHelpers.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/FormattingHelpers.cs @@ -572,5 +572,23 @@ public static bool IsInterpolation(this SyntaxToken currentToken) { return currentToken.Parent.IsKind(SyntaxKind.Interpolation); } + + /// + /// Checks whether currentToken is the opening paren of a deconstruction-declaration in var form, such as `var (x, y) = ...` + /// + public static bool IsOpenParenInVarDeconstructionDeclaration(this SyntaxToken currentToken) + { + SyntaxNode parent; + if (currentToken.Kind() == SyntaxKind.OpenParenToken + && (parent = currentToken.Parent)?.Kind() == SyntaxKind.VariableDeconstructionDeclarator + && (parent = parent.Parent)?.Kind() == SyntaxKind.VariableDeclaration) + { + var declaration = ((VariableDeclarationSyntax)parent); + + return (declaration.IsDeconstructionDeclaration) && (declaration.Type != null); + } + + return false; + } } } diff --git a/src/Workspaces/CSharp/Portable/Formatting/Rules/SpacingFormattingRule.cs b/src/Workspaces/CSharp/Portable/Formatting/Rules/SpacingFormattingRule.cs index 5732be25b12e9..73b746dd6c031 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/Rules/SpacingFormattingRule.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/Rules/SpacingFormattingRule.cs @@ -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 (currentToken.IsOpenParenInVarDeconstructionDeclaration()) + { + return CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); + } + return nextOperation.Invoke(); } diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index 9db54e4448263..f6a9697dbc84b 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -4420,6 +4420,48 @@ void bar() await AssertFormatAsync(expectedCode, code); } + [Fact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task SpacingInDeconstruction() + { + var code = @"class Class5{ +void bar() +{ +var(x,y)=(1,2); +} +}"; + var expectedCode = @"class Class5 +{ + void bar() + { + var (x, y) = (1, 2); + } +}"; + + await AssertFormatAsync(expectedCode, code); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task SpacingInNestedDeconstruction() + { + var code = @"class Class5{ +void bar() +{ +( int x1 , var( x2,x3 ) )=(1,(2,3)); +} +}"; + var expectedCode = @"class Class5 +{ + void bar() + { + (int x1, var (x2, x3)) = (1, (2, 3)); + } +}"; + + await AssertFormatAsync(expectedCode, code); + } + [Fact] [WorkItem(545335, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545335")] [Trait(Traits.Feature, Traits.Features.Formatting)]