diff --git a/docs/features/deconstruction.md b/docs/features/deconstruction.md
index 8c04e0d315743..945498dff3667 100644
--- a/docs/features/deconstruction.md
+++ b/docs/features/deconstruction.md
@@ -30,48 +30,61 @@ Treat deconstruction of a tuple into existing variables as a kind of assignment,
###Deconstruction-assignment (deconstruction into existing variables):
+
This doesn't introduce any changes to the language grammar. We have an `assignment-expression` (also simply called `assignment` in the C# grammar) where the `unary-expression` (the left-hand-side) is a `tuple-literal`.
-In short, what this does is find a `Deconstruct` method on the expression on the right-hand-side of the assignment, invoke it, collect its `out` parameters and assign them to the variables on the left-hand-side.
+In short, what this does in the general case is find a `Deconstruct` method on the expression on the right-hand-side of the assignment, invoke it, collect its `out` parameters and assign them to the variables on the left-hand-side. And in the special case where the expression on the right-hand-side is a tuple (tuple literal or tuple type), then the elements of the tuple can be assigned to the variables on the left-hand-side without needing to call `Deconstruct`.
The existing assignment binding currently checks if the variable on its left-hand-side can be assigned to and if the two sides are compatible.
It will be updated to support deconstruction-assignment, ie. when the left-hand-side is a tuple-literal/tuple-expression:
-- Each item on the left needs to be assignable and needs to be compatible with corresponding position on the right
+- Needs to break the right-hand-side into items. That step is un-necessary if the right-hand-side is already a tuple though.
+- Each item on the left needs to be assignable and needs to be compatible with corresponding position on the right (resulting from previous step).
- Needs to handle nesting case such as `(x, (y, z)) = M();`, but note that the second item in the top-level group has no discernable type.
-The lowering for deconstruction-assignment would translate: `(expressionX, expressionY, expressionZ) = (expressionA, expressionB, expressionC)` into:
+The evaluation order can be summarized as: (1) all the side-effects on the left-hand-side, (2) all the Deconstruct invocations (if not tuple), (3) conversions (if needed), and (4) assignments.
+
+In the general case, the lowering for deconstruction-assignment would translate: `(expressionX, expressionY, expressionZ) = expressionRight` into:
```
+// do LHS side-effects
tempX = &evaluate expressionX
tempY = &evaluate expressionY
tempZ = &evaluate expressionZ
-tempRight = evaluate right and evaluate Deconstruct
+// do Deconstruct
+evaluate right and evaluate Deconstruct in three parts (tempA, tempB and tempC)
-tempX = tempRight.A (including conversions)
-tempY = tempRight.B (including conversions)
-tempZ = tempRight.C (including conversions)
+// do conversions
+tempConvA = convert tempA
+tempConvB = convert tempB
+tempConvC = convert tempC
-“return/continue” with newTupleIncludingNames tempRight (so you can do get Item1 from the assignment)?
+// do assignments
+tempX = tempConvA
+tempY = tempConvB
+tempZ = tempConvC
```
The evaluation order for nesting `(x, (y, z))` is:
```
+// do LHS side-effects
tempX = &evaluate expressionX
-
-tempRight = evaluate right and evaluate Deconstruct
-
-tempX = tempRight.A (including conversions)
-tempLNested = tempRight.B (no conversions)
-
tempY = &evaluate expressionY
tempZ = &evaluate expressionZ
-tempRNest = evaluate Deconstruct on tempRight
+// do Deconstruct
+evaluate right and evaluate Deconstruct into two parts (tempA and tempNested)
+evaluate Deconstruct on tempNested intwo two parts (tempB and tempC)
-tempY = tempRNest.B (including conversions)
-tempZ = tempRNest.C (including conversions)
+// do conversions
+tempConvA = convert tempA
+tempConvB = convert tempB
+tempConvC = convert tempC
+// do assignments
+tempX = tempConvA
+tempY = tempConvB
+tempZ = tempConvC
```
The evaluation order for the simplest cases (locals, fields, array indexers, or anything returning ref) without needing conversion:
@@ -80,23 +93,15 @@ evaluate side-effect on the left-hand-side variables
evaluate Deconstruct passing the references directly in
```
-Note that the feature is built around the `Deconstruct` mechanism for deconstructing types.
-`ValueTuple` and `System.Tuple` will rely on that same mechanism, except that the compiler may need to synthesize the proper `Deconstruct` methods.
-
+In the case where the expression on the right is a tuple, the evaluation order becomes:
+```
+evaluate side-effect on the left-hand-side variables
+evaluate the right-hand-side and do a tuple conversion (using a fake tuple representing the types in the left-hand-side)
+assign element-wise from the right to the left
+```
-**Work items, open issues and assumptions to confirm with LDM:**
+Note that tuples (`System.ValueTuple` and `System.Tuple`) don't need to invoke Deconstruct.
-- I assume this should work even if `System.ValueTuple` is not present.
-- How is the Deconstruct method resolved?
- - I assumed there can be no ambiguity. Only one `Deconstruct` is allowed (in nesting cases we have no type to guide the resolution process).
- - But we may allow a little bit of ambiguity and preferring an instance over extension method.
-- Do the names matter? `int x, y; (a: x, b: y) = M();`
-- Can we deconstruct into a single out variable? I assume no.
-- I assume no compound assignment `(x, y) += M();`
-- [ ] Provide more details on the semantic of deconstruction-assignment, both static (The LHS of the an assignment-expression used be a L-value, but now it can be L-value -- which uses existing rules -- or tuple_literal. The new rules for tuple_literal on the LHS...) and dynamic.
-- [ ] Discuss with Aleksey about "Deconstruct and flow analysis for nullable ref type"
-- [ ] Validate any target typing or type inference scenarios.
-- The deconstruction-assignment is treated separately from deconstruction-declaration, which means it doesn't allow combinations such as `int x; (x, int y) = M();`.
###Deconstruction-declaration (deconstruction into new variables):
@@ -158,20 +163,11 @@ constant_declarator // not sure
;
```
-Treat deconstruction of a tuple into new variables as a new kind of node (AssignmentExpression).
+Treat deconstruction of a tuple into new variables as a new kind of node (not AssignmentExpression).
It would pick up the behavior of each contexts where new variables can be declared (TODO: need to list). For instance, in LINQ, new variables go into a transparent identifiers.
It is seen as deconstructing into separate variables (we don't introduce transparent identifiers in contexts where they didn't exist previously).
-Should we allow this?
-`var t = (x: 1, y: 2); (x: var a, y: var b) = t;`
-or `var (x: a, y: b) = t;`
-(if not, tuple names aren't very useful?)
-
-- [ ] Add example: var (x, y) =
-- [ ] Semantic (cardinality should match, ordering including conversion,
-- [ ] What are the type rules? `(string s, int x) = (null, 3);`
-
-- Deconstruction for `System.ValueTuple`, `System.Tuple` and any other type involves a call to `Deconstruct`.
+Just like deconstruction-assignment, deconstruction-declaration does not need to invoke `Deconstruct` for tuple types.
**References**
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index 78cf18b58d216..8caeda3dd6dae 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -1715,6 +1715,13 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, Diagnost
return BindAssignment(node, op1, op2, diagnostics);
}
+ ///
+ /// There are two kinds of deconstruction-assignments which this binding handles: tuple and non-tuple.
+ ///
+ /// Returns a BoundDeconstructionAssignmentOperator
+ /// - with all the fields populated except deconstructMember, for the tuple case
+ /// - with all the fields populated, for a non-tuple case
+ ///
private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
{
SeparatedSyntaxList arguments = ((TupleExpressionSyntax)node.Left).Arguments;
@@ -1727,9 +1734,9 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax
// bind the variables and check they can be assigned to
var checkedVariablesBuilder = ArrayBuilder.GetInstance(numElements);
- for (int i = 0; i < numElements; i++)
+ foreach (var argument in arguments)
{
- var boundVariable = BindExpression(arguments[i].Expression, diagnostics, invoked: false, indexed: false);
+ var boundVariable = BindExpression(argument.Expression, diagnostics, invoked: false, indexed: false);
var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignment, diagnostics);
checkedVariablesBuilder.Add(checkedVariable);
@@ -1737,6 +1744,40 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax
var checkedVariables = checkedVariablesBuilder.ToImmutableAndFree();
+ // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())`
+ if (boundRHS.Kind == BoundKind.TupleLiteral || ((object)boundRHS.Type != null && boundRHS.Type.IsTupleType))
+ {
+ return BindDeconstructWithTuple(node, diagnostics, boundRHS, checkedVariables);
+ }
+
+ // expression without type such as `null`
+ if (boundRHS.Type == null)
+ {
+ Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node);
+ return BadExpression(node, checkedVariables.Concat(boundRHS).ToArray());
+ }
+
+ return BindDeconstructWithDeconstruct(node, diagnostics, boundRHS, checkedVariables);
+ }
+
+ private BoundExpression BindDeconstructWithTuple(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression boundRHS, ImmutableArray checkedVariables)
+ {
+ // make a conversion for the tuple based on the LHS information (this also takes care of tuple literals without a natural type)
+ var lhsTypes = checkedVariables.SelectAsArray(v => v.Type);
+ TypeSymbol lhsAsTuple = TupleTypeSymbol.Create(locationOpt: null, elementTypes: lhsTypes, elementLocations: default(ImmutableArray), elementNames: default(ImmutableArray), compilation: Compilation, diagnostics: diagnostics);
+ var typedRHS = GenerateConversionForAssignment(lhsAsTuple, boundRHS, diagnostics);
+
+ ImmutableArray tupleTypes = typedRHS.Type.TupleElementTypes;
+
+ // figure out the pairwise conversions
+ var assignments = checkedVariables.SelectAsArray((variable, index, types) => MakeAssignmentInfo(variable, types[index], node, diagnostics), tupleTypes);
+
+ TypeSymbol lastType = tupleTypes.Last();
+ return new BoundDeconstructionAssignmentOperator(node, checkedVariables, typedRHS, deconstructMemberOpt: null, assignments: assignments, type: lastType);
+ }
+
+ private BoundExpression BindDeconstructWithDeconstruct(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression boundRHS, ImmutableArray checkedVariables)
+ {
// symbol and parameters for Deconstruct
DiagnosticBag bag = new DiagnosticBag();
MethodSymbol deconstructMethod = FindDeconstruct(checkedVariables, boundRHS, node, bag);
@@ -1754,24 +1795,27 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax
}
// figure out the pairwise conversions
- var assignmentsBuilder = ArrayBuilder.GetInstance(numElements);
var deconstructParameters = deconstructMethod.Parameters;
- for (int i = 0; i < checkedVariables.Length; i++)
- {
- var leftPlaceholder = new BoundLValuePlaceholder(checkedVariables[i].Syntax, checkedVariables[i].Type) { WasCompilerGenerated = true };
- var rightPlaceholder = new BoundRValuePlaceholder(node.Right, deconstructParameters[i].Type) { WasCompilerGenerated = true };
-
- // each assignment has a placeholder for a receiver and another for the source
- BoundAssignmentOperator op = BindAssignment(node, leftPlaceholder, rightPlaceholder, diagnostics);
- assignmentsBuilder.Add(new BoundDeconstructionAssignmentOperator.AssignmentInfo() { Assignment = op, LValuePlaceholder = leftPlaceholder, RValuePlaceholder = rightPlaceholder });
- }
-
- var assignments = assignmentsBuilder.ToImmutableAndFree();
+ var assignments = checkedVariables.SelectAsArray((variable, index, parameters) => MakeAssignmentInfo(variable, parameters[index].Type, node, diagnostics), deconstructParameters);
TypeSymbol lastType = deconstructParameters.Last().Type;
return new BoundDeconstructionAssignmentOperator(node, checkedVariables, boundRHS, deconstructMethod, assignments, lastType);
}
+ ///
+ /// Figures out how to assign from sourceType into receivingVariable and bundles the information (leaving holes for the actual source and receiver) into an AssignmentInfo.
+ ///
+ private BoundDeconstructionAssignmentOperator.AssignmentInfo MakeAssignmentInfo(BoundExpression receivingVariable, TypeSymbol sourceType, AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
+ {
+ var leftPlaceholder = new BoundLValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true };
+ var rightPlaceholder = new BoundRValuePlaceholder(node.Right, sourceType) { WasCompilerGenerated = true };
+
+ // each assignment has a placeholder for a receiver and another for the source
+ BoundAssignmentOperator op = BindAssignment(node, leftPlaceholder, rightPlaceholder, diagnostics);
+
+ return new BoundDeconstructionAssignmentOperator.AssignmentInfo() { Assignment = op, LValuePlaceholder = leftPlaceholder, RValuePlaceholder = rightPlaceholder };
+ }
+
///
/// Find the Deconstruct method for the expression on the right, that will fit the assignable bound expressions on the left.
/// Returns true if the Deconstruct method is found.
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
index 144df264efbd0..818c9a92dd5d5 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
@@ -397,7 +397,7 @@
-
+
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs
index 6f712678266d9..3818d7c9464a0 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs
+++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs
@@ -3094,6 +3094,15 @@ internal class CSharpResources {
}
}
+ ///
+ /// Looks up a localized string similar to Deconstruct assignment requires an expression with a type on the right-hand-side..
+ ///
+ internal static string ERR_DeconstructRequiresExpression {
+ get {
+ return ResourceManager.GetString("ERR_DeconstructRequiresExpression", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The Deconstruct method for type '{0}' must have only out parameters..
///
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 6210d481314bb..1bfd74d4da545 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -4884,4 +4884,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
No Deconstruct instance or extension method was found for type '{0}'.
+
+ Deconstruct assignment requires an expression with a type on the right-hand-side.
+
\ No newline at end of file
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
index 3e5fa88eeb6d7..228829f80695d 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
@@ -1395,5 +1395,6 @@ internal enum ErrorCode
ERR_AmbiguousDeconstruct = 8207,
ERR_DeconstructRequiresOutParams = 8208,
ERR_DeconstructWrongParams = 8209,
+ ERR_DeconstructRequiresExpression = 8210,
}
}
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
index 9b025171a90d9..933ff1ebc1777 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
@@ -1,6 +1,9 @@
// 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;
+using System.Collections.Immutable;
using System.Diagnostics;
+using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
namespace Microsoft.CodeAnalysis.CSharp
@@ -9,9 +12,6 @@ internal sealed partial class LocalRewriter
{
public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node)
{
- CSharpSyntaxNode syntax = node.Syntax;
- int numVariables = node.LeftVariables.Length;
-
var temps = ArrayBuilder.GetInstance();
var stores = ArrayBuilder.GetInstance();
@@ -24,40 +24,27 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
}
BoundExpression loweredRight = VisitExpression(node.Right);
+ ImmutableArray rhsValues;
- // prepare out parameters for Deconstruct
- var deconstructParameters = node.DeconstructMember.Parameters;
- var outParametersBuilder = ArrayBuilder.GetInstance(deconstructParameters.Length);
- Debug.Assert(deconstructParameters.Length == node.LeftVariables.Length);
-
- for (int i = 0; i < numVariables; i++)
+ // get or make right-hand-side values
+ if (node.Right.Type.IsTupleType)
{
- var localSymbol = new SynthesizedLocal(_factory.CurrentMethod, deconstructParameters[i].Type, SynthesizedLocalKind.LoweringTemp);
-
- var localBound = new BoundLocal(syntax,
- localSymbol,
- null,
- deconstructParameters[i].Type
- ) { WasCompilerGenerated = true };
-
- temps.Add(localSymbol);
- outParametersBuilder.Add(localBound);
+ rhsValues = AccessTupleFields(node, loweredRight, temps, stores);
+ }
+ else
+ {
+ rhsValues = CallDeconstruct(node, loweredRight, temps, stores);
}
- var outParameters = outParametersBuilder.ToImmutableAndFree();
-
- // invoke Deconstruct
- var invokeDeconstruct = MakeCall(syntax, loweredRight, node.DeconstructMember, outParameters, node.DeconstructMember.ReturnType);
- stores.Add(invokeDeconstruct);
-
- // assign from out temps to lhs receivers
- for (int i = 0; i < numVariables; i++)
+ // assign from rhs values to lhs receivers
+ int numAssignments = node.Assignments.Length;
+ for (int i = 0; i < numAssignments; i++)
{
// lower the assignment and replace the placeholders for source and target in the process
var assignmentInfo = node.Assignments[i];
AddPlaceholderReplacement(assignmentInfo.LValuePlaceholder, lhsReceivers[i]);
- AddPlaceholderReplacement(assignmentInfo.RValuePlaceholder, outParameters[i]);
+ AddPlaceholderReplacement(assignmentInfo.RValuePlaceholder, rhsValues[i]);
var assignment = VisitExpression(assignmentInfo.Assignment);
@@ -75,5 +62,81 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
return result;
}
+
+ private ImmutableArray AccessTupleFields(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder temps, ArrayBuilder stores)
+ {
+ var tupleType = loweredRight.Type.IsTupleType ? loweredRight.Type : TupleTypeSymbol.Create((NamedTypeSymbol)loweredRight.Type);
+ var tupleElementTypes = tupleType.TupleElementTypes;
+
+ var numElements = tupleElementTypes.Length;
+ Debug.Assert(numElements == node.LeftVariables.Length);
+
+ CSharpSyntaxNode syntax = node.Syntax;
+
+ // save the loweredRight as we need to access it multiple times
+ BoundAssignmentOperator assignmentToTemp;
+ var savedTuple = _factory.StoreToTemp(loweredRight, out assignmentToTemp);
+ stores.Add(assignmentToTemp);
+ temps.Add(savedTuple.LocalSymbol);
+
+ // list the tuple fields accessors
+ var fieldAccessorsBuilder = ArrayBuilder.GetInstance(numElements);
+ var fields = tupleType.TupleElementFields;
+
+ for (int i = 0; i < numElements; i++)
+ {
+ var field = fields[i];
+
+ DiagnosticInfo useSiteInfo = field.GetUseSiteDiagnostic();
+ if ((object)useSiteInfo != null && useSiteInfo.Severity == DiagnosticSeverity.Error)
+ {
+ Symbol.ReportUseSiteDiagnostic(useSiteInfo, _diagnostics, syntax.Location);
+ }
+ var fieldAccess = MakeTupleFieldAccess(syntax, field, savedTuple, null, LookupResultKind.Empty, tupleElementTypes[i]);
+ fieldAccessorsBuilder.Add(fieldAccess);
+ }
+
+ return fieldAccessorsBuilder.ToImmutableAndFree();
+ }
+
+ ///
+ /// Prepares local variables to be used in Deconstruct call
+ /// Adds a invocation of Deconstruct with those as out parameters onto the 'stores' sequence
+ /// Returns the expressions for those out parameters
+ ///
+ private ImmutableArray CallDeconstruct(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder temps, ArrayBuilder stores)
+ {
+ Debug.Assert((object)node.DeconstructMemberOpt != null);
+
+ CSharpSyntaxNode syntax = node.Syntax;
+
+ // prepare out parameters for Deconstruct
+ var deconstructParameters = node.DeconstructMemberOpt.Parameters;
+ var outParametersBuilder = ArrayBuilder.GetInstance(deconstructParameters.Length);
+ Debug.Assert(deconstructParameters.Length == node.LeftVariables.Length);
+
+ foreach (var deconstructParameter in deconstructParameters)
+ {
+ var localSymbol = new SynthesizedLocal(_factory.CurrentMethod, deconstructParameter.Type, SynthesizedLocalKind.LoweringTemp);
+
+ var localBound = new BoundLocal(syntax,
+ localSymbol,
+ null,
+ deconstructParameter.Type
+ )
+ { WasCompilerGenerated = true };
+
+ temps.Add(localSymbol);
+ outParametersBuilder.Add(localBound);
+ }
+
+ var outParameters = outParametersBuilder.ToImmutableAndFree();
+
+ // invoke Deconstruct
+ var invokeDeconstruct = MakeCall(syntax, loweredRight, node.DeconstructMemberOpt, outParameters, node.DeconstructMemberOpt.ReturnType);
+ stores.Add(invokeDeconstruct);
+
+ return outParameters;
+ }
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
index bd3d82b812a08..497d002609a82 100644
--- a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
@@ -397,6 +397,19 @@ public virtual FieldSymbol TupleUnderlyingField
}
}
+ ///
+ /// If this is a field representing a tuple element,
+ /// returns the index of the element (zero-based).
+ /// Otherwise, a negative number.
+ ///
+ public virtual int TupleElementIndex
+ {
+ get
+ {
+ return -1;
+ }
+ }
+
#region IFieldSymbol Members
ISymbol IFieldSymbol.AssociatedSymbol
diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleErrorFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleErrorFieldSymbol.cs
index a1133cd6d024b..5c45297281321 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleErrorFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleErrorFieldSymbol.cs
@@ -48,7 +48,7 @@ public override bool IsTupleField
/// id is an index of the element (zero-based).
/// Otherwise, (-1 - [index in members array]);
///
- public int TupleFieldId
+ public override int TupleElementIndex
{
get
{
diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
index 60a6394779154..7826371890ad9 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
@@ -40,8 +40,8 @@ public override bool IsTupleField
/// If this field represents a tuple element (including the name match),
/// id is an index of the element (zero-based).
/// Otherwise, (-1 - [index in members array]);
- ///
- public int TupleFieldId
+ /// i
+ public override int TupleElementIndex
{
get
{
diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
index 27f5277733b82..4c60fc8babd80 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
@@ -39,6 +39,7 @@ internal sealed class TupleTypeSymbol : WrappedNamedTypeSymbol
private readonly ImmutableArray _elementTypes;
private ImmutableArray _lazyMembers;
+ private ImmutableArray _lazyFields;
private SmallDictionary _lazyUnderlyingDefinitionToMemberMap;
internal const int RestPosition = 8; // The Rest field is in 8th position
@@ -666,6 +667,47 @@ public override ImmutableArray TupleElementNames
}
}
+ ///
+ /// Get the fields for the tuple's elements (in order and cached).
+ ///
+ public override ImmutableArray TupleElementFields
+ {
+ get
+ {
+ if (_lazyFields.IsDefault)
+ {
+ ImmutableInterlocked.InterlockedInitialize(ref _lazyFields, CollectTupleElementFields());
+ }
+
+ return _lazyFields;
+ }
+ }
+
+ private ImmutableArray CollectTupleElementFields()
+ {
+ var builder = ArrayBuilder.GetInstance(_elementTypes.Length, null);
+
+ foreach (var member in GetMembers())
+ {
+ if (member.Kind != SymbolKind.Field)
+ {
+ continue;
+ }
+
+ var field = (FieldSymbol)member;
+ int index = field.TupleElementIndex;
+ if (index >= 0)
+ {
+ Debug.Assert((object)builder[index] == null);
+ builder[index] = field;
+ }
+ }
+
+ Debug.Assert(builder.All(symbol => (object)symbol != null));
+
+ return builder.ToImmutableAndFree();
+ }
+
///
/// Returns all members of the tuple type - a combination of members from the underlying type
/// and synthesized fields for tuple elements.
diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs
index 5b2c0461be42f..0a10860fa0db9 100644
--- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs
@@ -619,6 +619,12 @@ public virtual NamedTypeSymbol TupleUnderlyingType
///
public virtual ImmutableArray TupleElementNames => default(ImmutableArray);
+ ///
+ /// If this symbol represents a tuple type, get the fields for the tuple's elements.
+ /// Otherwise, returns default.
+ ///
+ public virtual ImmutableArray TupleElementFields => default(ImmutableArray);
+
///
/// Is this type a managed type (false for everything but enum, pointer, and
/// some struct types).
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
index 9d518cabfe376..e1195b0a27a52 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
@@ -1,5 +1,6 @@
// 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.Linq;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
@@ -34,6 +35,28 @@ public void Deconstruct(out int a, out string b)
var comp = CompileAndVerify(source, expectedOutput: "1 hello", parseOptions: TestOptions.Regular.WithTuplesFeature());
comp.VerifyDiagnostics();
+ comp.VerifyIL("C.Main", @"
+{
+ // Code size 40 (0x28)
+ .maxstack 3
+ .locals init (string V_0, //y
+ int V_1,
+ string V_2)
+ IL_0000: newobj ""C..ctor()""
+ IL_0005: ldloca.s V_1
+ IL_0007: ldloca.s V_2
+ IL_0009: call ""void C.Deconstruct(out int, out string)""
+ IL_000e: ldloc.1
+ IL_000f: conv.i8
+ IL_0010: ldloc.2
+ IL_0011: stloc.0
+ IL_0012: box ""long""
+ IL_0017: ldstr "" ""
+ IL_001c: ldloc.0
+ IL_001d: call ""string string.Concat(object, object, object)""
+ IL_0022: call ""void System.Console.WriteLine(string)""
+ IL_0027: ret
+}");
}
[Fact]
@@ -559,5 +582,731 @@ static void Main()
Diagnostic(ErrorCode.ERR_InvalidExprTerm, ".").WithArguments(".").WithLocation(6, 24)
);
}
+
+ [Fact]
+ [CompilerTrait(CompilerFeature.RefLocalsReturns)]
+ public void RefReturningMethod()
+ {
+ string source = @"
+class C
+{
+ static int i = 0;
+
+ static void Main()
+ {
+ (M(), M()) = new C();
+ System.Console.WriteLine($""Final i is {i}"");
+ }
+
+ static ref int M()
+ {
+ System.Console.WriteLine($""M (previous i is {i})"");
+ return ref i;
+ }
+
+ void Deconstruct(out int x, out int y)
+ {
+ System.Console.WriteLine(""Deconstruct"");
+ x = 42;
+ y = 43;
+ }
+}
+";
+ var expected =
+@"M (previous i is 0)
+M (previous i is 0)
+Deconstruct
+Final i is 43
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics(
+ );
+ }
+
+ [Fact]
+ [CompilerTrait(CompilerFeature.RefLocalsReturns)]
+ public void RefReturningProperty()
+ {
+ string source = @"
+class C
+{
+ static int i = 0;
+
+ static void Main()
+ {
+ (P, P) = new C();
+ System.Console.WriteLine($""Final i is {i}"");
+ }
+
+ static ref int P
+ {
+ get
+ {
+ System.Console.WriteLine($""P (previous i is {i})"");
+ return ref i;
+ }
+ }
+
+ void Deconstruct(out int x, out int y)
+ {
+ System.Console.WriteLine(""Deconstruct"");
+ x = 42;
+ y = 43;
+ }
+}
+";
+ var expected =
+@"P (previous i is 0)
+P (previous i is 0)
+Deconstruct
+Final i is 43
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics(
+ );
+ }
+
+ [Fact(Skip = "PROTOTYPE(tuples)")]
+ [CompilerTrait(CompilerFeature.RefLocalsReturns)]
+ public void RefReturningMethod2()
+ {
+ string source = @"
+class C
+{
+ static int i;
+
+ static void Main()
+ {
+ (M(), M()) = new C();
+ }
+
+ static ref int M()
+ {
+ System.Console.WriteLine(""M"");
+ return ref i;
+ }
+
+ void Deconstruct(out int i, out int j)
+ {
+ i = 42;
+ j = 43;
+ }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics();
+
+ // This error is wrong
+ // (4,16): warning CS0649: Field 'C.i' is never assigned to, and will always have its default value 0
+ // static int i;
+ //Diagnostic(ErrorCode.WRN_UnassignedInternalField, "i").WithArguments("C.i", "0").WithLocation(4, 16)
+ }
+
+ [Fact(Skip = "PROTOTYPE(tuples)")]
+ [CompilerTrait(CompilerFeature.RefLocalsReturns)]
+ public void RefReturningMethodFlow()
+ {
+ string source = @"
+struct C
+{
+ static C i;
+ static C P { get { System.Console.WriteLine(""getP""); return i; } set { System.Console.WriteLine(""setP""); i = value; } }
+
+ static void Main()
+ {
+ (M(), M()) = P;
+ }
+
+ static ref C M()
+ {
+ System.Console.WriteLine($""M (previous i is {i})"");
+ return ref i;
+ }
+
+ void Deconstruct(out int x, out int y)
+ {
+ System.Console.WriteLine(""Deconstruct"");
+ x = 42;
+ y = 43;
+ }
+
+ public static implicit operator C(int x)
+ {
+ System.Console.WriteLine(""conversion"");
+ return new C();
+ }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics();
+
+
+ var expected =
+ @"";
+
+ // Should not crash!
+ var comp2 = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp2.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void UninitializedRight()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ int x;
+ (x, x) = x;
+ }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics(
+ // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'int'.
+ // (x, x) = x;
+ Diagnostic(ErrorCode.ERR_MissingDeconstruct, "x").WithArguments("int").WithLocation(7, 18),
+ // (7,18): error CS0165: Use of unassigned local variable 'x'
+ // (x, x) = x;
+ Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 18)
+ );
+ }
+
+ [Fact]
+ public void Indexers()
+ {
+ string source = @"
+class C
+{
+ static SomeArray array;
+
+ static void Main()
+ {
+ int y;
+ (Foo()[Bar()], y) = new C();
+ System.Console.WriteLine($""Final array values[2] {array.values[2]}"");
+ }
+
+ static SomeArray Foo()
+ {
+ System.Console.WriteLine($""Foo"");
+ array = new SomeArray();
+ return array;
+ }
+
+ static int Bar()
+ {
+ System.Console.WriteLine($""Bar"");
+ return 2;
+ }
+
+ void Deconstruct(out int x, out int y)
+ {
+ System.Console.WriteLine(""Deconstruct"");
+ x = 101;
+ y = 102;
+ }
+}
+class SomeArray
+{
+ public int[] values;
+ public SomeArray() { values = new [] { 42, 43, 44 }; }
+ public int this[int index] {
+ get { System.Console.WriteLine($""indexGet (with value {values[index]})""); return values[index]; }
+ set { System.Console.WriteLine($""indexSet (with value {value})""); values[index] = value; }
+ }
+}
+";
+ var expected =
+@"Foo
+Bar
+Deconstruct
+indexSet (with value 101)
+Final array values[2] 101
+";
+ var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics(
+ );
+ }
+
+ [Fact]
+ public void NullRight()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ int x;
+ (x, x) = null;
+ }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics(
+ // (7,9): 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),
+ // (7,10): error CS0165: Use of unassigned local variable 'x'
+ // (x, x) = null;
+ Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 10)
+ );
+ }
+
+ [Fact]
+ public void VoidRight()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ int x;
+ (x, x) = M();
+ }
+ static void M() { }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics(
+ // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'void'.
+ // (x, x) = M();
+ Diagnostic(ErrorCode.ERR_MissingDeconstruct, "M()").WithArguments("void").WithLocation(7, 18)
+ );
+ }
+
+ [Fact]
+ public void DeconstructTuple()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ long x;
+ string y;
+
+ (x, y) = (1, ""hello"");
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact(Skip = "PROTOTYPE(tuples)")]
+ public void DeconstructTuple2()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ long x;
+ string y;
+
+ (x, y) = M();
+ System.Console.WriteLine(x + "" "" + y);
+ }
+
+ static System.ValueTuple M()
+ {
+ return (1, ""hello"");
+ }
+}
+";
+ // Should not give this error: (9,18): error CS0029: Cannot implicitly convert type '(int, string)' to '(long, string)'
+ var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void DeconstructLongTuple()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ long x;
+ int y;
+
+ (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, 2);
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ comp.VerifyIL("C.Main", @"
+{
+ // Code size 141 (0x8d)
+ .maxstack 10
+ .locals init (long V_0, //x
+ int V_1) //y
+ IL_0000: ldc.i4.1
+ IL_0001: conv.i8
+ IL_0002: ldc.i4.1
+ IL_0003: conv.i8
+ IL_0004: ldc.i4.1
+ IL_0005: conv.i8
+ IL_0006: ldc.i4.1
+ IL_0007: conv.i8
+ IL_0008: ldc.i4.1
+ IL_0009: conv.i8
+ IL_000a: ldc.i4.1
+ IL_000b: conv.i8
+ IL_000c: ldc.i4.1
+ IL_000d: conv.i8
+ IL_000e: ldc.i4.1
+ IL_000f: conv.i8
+ IL_0010: ldc.i4.4
+ IL_0011: conv.i8
+ IL_0012: ldc.i4.2
+ IL_0013: newobj ""System.ValueTuple..ctor(long, long, int)""
+ IL_0018: newobj ""System.ValueTuple..ctor(long, long, long, long, long, long, long, (long, long, int))""
+ IL_001d: dup
+ IL_001e: ldfld ""long System.ValueTuple.Item1""
+ IL_0023: stloc.0
+ IL_0024: dup
+ IL_0025: ldfld ""long System.ValueTuple.Item2""
+ IL_002a: stloc.0
+ IL_002b: dup
+ IL_002c: ldfld ""long System.ValueTuple.Item3""
+ IL_0031: stloc.0
+ IL_0032: dup
+ IL_0033: ldfld ""long System.ValueTuple.Item4""
+ IL_0038: stloc.0
+ IL_0039: dup
+ IL_003a: ldfld ""long System.ValueTuple.Item5""
+ IL_003f: stloc.0
+ IL_0040: dup
+ IL_0041: ldfld ""long System.ValueTuple.Item6""
+ IL_0046: stloc.0
+ IL_0047: dup
+ IL_0048: ldfld ""long System.ValueTuple.Item7""
+ IL_004d: stloc.0
+ IL_004e: dup
+ IL_004f: ldfld ""(long, long, int) System.ValueTuple.Rest""
+ IL_0054: ldfld ""long System.ValueTuple.Item1""
+ IL_0059: stloc.0
+ IL_005a: dup
+ IL_005b: ldfld ""(long, long, int) System.ValueTuple.Rest""
+ IL_0060: ldfld ""long System.ValueTuple.Item2""
+ IL_0065: stloc.0
+ IL_0066: ldfld ""(long, long, int) System.ValueTuple.Rest""
+ IL_006b: ldfld ""int System.ValueTuple.Item3""
+ IL_0070: stloc.1
+ IL_0071: ldloc.0
+ IL_0072: box ""long""
+ IL_0077: ldstr "" ""
+ IL_007c: ldloc.1
+ IL_007d: box ""int""
+ IL_0082: call ""string string.Concat(object, object, object)""
+ IL_0087: call ""void System.Console.WriteLine(string)""
+ IL_008c: ret
+}
+");
+ }
+
+ [Fact]
+ public void DeconstructLongTupleWithNames()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ long x;
+ int y;
+
+ (x, x, x, x, x, x, x, x, x, y) = (a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10);
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "9 10", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact(Skip = "PROTOTYPE(tuples)")]
+ public void DeconstructLongTuple2()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ long x;
+ int y;
+
+ (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, (byte)2);
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ // issue with return type
+ var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void DeconstructTypelessTuple()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ string x = ""goodbye"";
+ string y;
+
+ (x, y) = (null, ""hello"");
+ System.Console.WriteLine($""{x}{y}"");
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ comp.VerifyIL("C.Main", @"
+{
+ // Code size 48 (0x30)
+ .maxstack 3
+ .locals init (string V_0, //x
+ string V_1) //y
+ IL_0000: ldstr ""goodbye""
+ IL_0005: stloc.0
+ IL_0006: ldnull
+ IL_0007: ldstr ""hello""
+ IL_000c: newobj ""System.ValueTuple..ctor(string, string)""
+ IL_0011: dup
+ IL_0012: ldfld ""string System.ValueTuple.Item1""
+ IL_0017: stloc.0
+ IL_0018: ldfld ""string System.ValueTuple.Item2""
+ IL_001d: stloc.1
+ IL_001e: ldstr ""{0}{1}""
+ IL_0023: ldloc.0
+ IL_0024: ldloc.1
+ IL_0025: call ""string string.Format(string, object, object)""
+ IL_002a: call ""void System.Console.WriteLine(string)""
+ IL_002f: ret
+}
+");
+ }
+
+ [Fact]
+ public void TupleWithNoConversion()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ byte x;
+ string y;
+
+ (x, y) = (1, 2);
+ }
+}
+";
+ // PROTOTYPE(tuples) This error message is misleading
+ var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics(
+ // (9,18): error CS0029: Cannot implicitly convert type '(int, int)' to '(byte, string)'
+ // (x, y) = (1, 2);
+ Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("(int, int)", "(byte, string)").WithLocation(9, 18)
+ );
+ }
+
+ [Fact]
+ public void DeconstructIntoProperties()
+ {
+ string source = @"
+class C
+{
+ static long x { set { System.Console.WriteLine($""setX {value}""); } }
+ static string y { get; set; }
+
+ static void Main()
+ {
+ (x, y) = new C();
+ System.Console.WriteLine(y);
+ }
+
+ public void Deconstruct(out int a, out string b)
+ {
+ a = 1;
+ b = ""hello"";
+ }
+}
+";
+ string expected =
+@"setX 1
+hello";
+ var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void DeconstructTupleIntoProperties()
+ {
+ string source = @"
+class C
+{
+ static long x { set { System.Console.WriteLine($""setX {value}""); } }
+ static string y { get; set; }
+
+ static void Main()
+ {
+ (x, y) = (1, ""hello"");
+ System.Console.WriteLine(y);
+ }
+}
+";
+ string expected =
+@"setX 1
+hello";
+ var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void Swap()
+ {
+ string source = @"
+class C
+{
+ static int x = 2;
+ static int y = 4;
+
+ static void Main()
+ {
+ Swap();
+ System.Console.WriteLine(x + "" "" + y);
+ }
+
+ static void Swap()
+ {
+ (x, y) = (y, x);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ comp.VerifyIL("C.Swap", @"
+{
+ // Code size 37 (0x25)
+ .maxstack 2
+ IL_0000: ldsfld ""int C.y""
+ IL_0005: ldsfld ""int C.x""
+ IL_000a: newobj ""System.ValueTuple..ctor(int, int)""
+ IL_000f: dup
+ IL_0010: ldfld ""int System.ValueTuple.Item1""
+ IL_0015: stsfld ""int C.x""
+ IL_001a: ldfld ""int System.ValueTuple.Item2""
+ IL_001f: stsfld ""int C.y""
+ IL_0024: ret
+}
+");
+ }
+
+
+ [Fact]
+ public void TupleWithUseSiteError()
+ {
+ string source = @"
+
+namespace System
+{
+ public struct ValueTuple
+ {
+ public T1 Item1;
+
+ public ValueTuple(T1 item1, T2 item2)
+ {
+ this.Item1 = item1;
+ }
+ }
+}
+class C
+{
+ static void Main()
+ {
+ int x;
+ int y;
+
+ (x, y) = (1, 2);
+ }
+}
+";
+
+ var comp = CreateCompilationWithMscorlib(source, assemblyName: "comp", parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ comp.VerifyEmitDiagnostics(
+ // (22,9): error CS8205: Member 'Item2' was not found on type 'ValueTuple' from assembly 'comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
+ // (x, y) = (1, 2);
+ Diagnostic(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, "(x, y) = (1, 2)").WithArguments("Item2", "System.ValueTuple", "comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(22, 9)
+ );
+ }
+
+ [Fact]
+ public void CircularFlow()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ (object i, object ii) x = (1,2);
+ object y;
+
+ (x.ii, y) = x;
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ [CompilerTrait(CompilerFeature.RefLocalsReturns)]
+ public void CircularFlow2()
+ {
+ string source = @"
+class C
+{
+ static void Main()
+ {
+ (object i, object ii) x = (1,2);
+ object y;
+
+ ref var a = ref x;
+
+ (a.ii, y) = x;
+ System.Console.WriteLine(x + "" "" + y);
+ }
+}
+";
+
+ var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
+ comp.VerifyDiagnostics();
+ }
}
}
\ No newline at end of file