diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
index 366426e86abbe..88b47b52f8f0c 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
@@ -25,13 +25,13 @@ namespace Microsoft.CodeAnalysis.CSharp
///
internal partial class Binder
{
- private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
+ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, bool resultIsUsedOverride = false)
{
var left = node.Left;
var right = node.Right;
DeclarationExpressionSyntax declaration = null;
ExpressionSyntax expression = null;
- var result = BindDeconstruction(node, left, right, diagnostics, ref declaration, ref expression);
+ var result = BindDeconstruction(node, left, right, diagnostics, ref declaration, ref expression, resultIsUsedOverride);
if (declaration != null)
{
// only allowed at the top level, or in a for loop
@@ -78,6 +78,8 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
/// Where to report diagnostics
/// A variable set to the first variable declaration found in the left
/// A variable set to the first expression in the left that isn't a declaration or discard
+ /// The expression evaluator needs to bind deconstructions (both assignments and declarations) as expression-statements
+ /// and still access the returned value
///
///
internal BoundDeconstructionAssignmentOperator BindDeconstruction(
@@ -87,6 +89,7 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
DiagnosticBag diagnostics,
ref DeclarationExpressionSyntax declaration,
ref ExpressionSyntax expression,
+ bool resultIsUsedOverride = false,
BoundDeconstructValuePlaceholder rightPlaceholder = null)
{
DeconstructionVariable locals = BindDeconstructionVariables(left, diagnostics, ref declaration, ref expression);
@@ -95,7 +98,8 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
BoundExpression boundRight = rightPlaceholder ?? BindValue(right, diagnostics, BindValueKind.RValue);
boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, diagnostics);
- var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, diagnostics);
+ bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left);
+ var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, diagnostics);
DeconstructionVariable.FreeDeconstructionVariables(locals.NestedVariables);
return assignment;
@@ -106,6 +110,7 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
ExpressionSyntax left,
BoundExpression boundRHS,
ArrayBuilder checkedVariables,
+ bool resultIsUsed,
DiagnosticBag diagnostics)
{
if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType())
@@ -117,9 +122,10 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
var type = boundRHS.Type ?? voidType;
return new BoundDeconstructionAssignmentOperator(
node,
- DeconstructionVariablesAsTuple(node, checkedVariables, diagnostics, hasErrors: true),
+ DeconstructionVariablesAsTuple(node, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true),
new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false,
constantValueOpt: null, type: type, hasErrors: true),
+ resultIsUsed,
voidType,
hasErrors: true);
}
@@ -135,7 +141,7 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
FailRemainingInferences(checkedVariables, diagnostics);
- var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, hasErrors);
+ var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed);
TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type;
var boundConversion = new BoundConversion(
@@ -149,7 +155,38 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag
hasErrors: hasErrors)
{ WasCompilerGenerated = true };
- return new BoundDeconstructionAssignmentOperator(node, lhsTuple, boundConversion, returnType);
+ return new BoundDeconstructionAssignmentOperator(node, lhsTuple, boundConversion, resultIsUsed, returnType);
+ }
+
+ private static bool IsDeconstructionResultUsed(ExpressionSyntax left)
+ {
+ var parent = left.Parent;
+ if (parent is null || parent.Kind() == SyntaxKind.ForEachVariableStatement)
+ {
+ return false;
+ }
+
+ Debug.Assert(parent.Kind() == SyntaxKind.SimpleAssignmentExpression);
+
+ var grandParent = parent.Parent;
+ if (grandParent is null)
+ {
+ return false;
+ }
+
+ switch (grandParent.Kind())
+ {
+ case SyntaxKind.ExpressionStatement:
+ return ((ExpressionStatementSyntax)grandParent).Expression != parent;
+
+ case SyntaxKind.ForStatement:
+ // Incrementors and Initializers don't have to produce a value
+ var loop = (ForStatementSyntax)grandParent;
+ return !loop.Incrementors.Contains(parent) && !loop.Initializers.Contains(parent);
+
+ default:
+ return true;
+ }
}
/// When boundRHS is a tuple literal, fix it up by inferring its types.
@@ -467,7 +504,8 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder));
}
- private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, DiagnosticBag diagnostics, bool hasErrors)
+ private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables,
+ DiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple)
{
int count = variables.Count;
var valuesBuilder = ArrayBuilder.GetInstance(count);
@@ -478,7 +516,7 @@ private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax
{
if (variable.HasNestedVariables)
{
- var nestedTuple = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, hasErrors);
+ var nestedTuple = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple);
valuesBuilder.Add(nestedTuple);
typesBuilder.Add(nestedTuple.Type);
namesBuilder.Add(null);
@@ -504,9 +542,9 @@ private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax
var type = TupleTypeSymbol.Create(syntax.Location,
typesBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(),
tupleNames, this.Compilation,
- shouldCheckConstraints: !hasErrors,
- errorPositions: disallowInferredNames ? inferredPositions : default(ImmutableArray),
- syntax: syntax, diagnostics: hasErrors ? null : diagnostics);
+ shouldCheckConstraints: !ignoreDiagnosticsFromTuple,
+ errorPositions: disallowInferredNames ? inferredPositions : default,
+ syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics);
// Always track the inferred positions in the bound node, so that conversions don't produce a warning
// for "dropped names" on tuple literal when the name was inferred.
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
index fca8f89687e1d..ecff5f3222312 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
@@ -396,6 +396,7 @@
+
diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
index d624e3ee0bab3..d4e811604f9fe 100644
--- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
+++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs
@@ -1160,7 +1160,7 @@ public BoundAssignmentOperator Update(BoundExpression left, BoundExpression righ
internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpression
{
- public BoundDeconstructionAssignmentOperator(SyntaxNode syntax, BoundTupleExpression left, BoundConversion right, TypeSymbol type, bool hasErrors = false)
+ public BoundDeconstructionAssignmentOperator(SyntaxNode syntax, BoundTupleExpression left, BoundConversion right, Boolean isUsed, TypeSymbol type, bool hasErrors = false)
: base(BoundKind.DeconstructionAssignmentOperator, syntax, type, hasErrors || left.HasErrors() || right.HasErrors())
{
@@ -1170,6 +1170,7 @@ public BoundDeconstructionAssignmentOperator(SyntaxNode syntax, BoundTupleExpres
this.Left = left;
this.Right = right;
+ this.IsUsed = isUsed;
}
@@ -1177,16 +1178,18 @@ public BoundDeconstructionAssignmentOperator(SyntaxNode syntax, BoundTupleExpres
public BoundConversion Right { get; }
+ public Boolean IsUsed { get; }
+
public override BoundNode Accept(BoundTreeVisitor visitor)
{
return visitor.VisitDeconstructionAssignmentOperator(this);
}
- public BoundDeconstructionAssignmentOperator Update(BoundTupleExpression left, BoundConversion right, TypeSymbol type)
+ public BoundDeconstructionAssignmentOperator Update(BoundTupleExpression left, BoundConversion right, Boolean isUsed, TypeSymbol type)
{
- if (left != this.Left || right != this.Right || type != this.Type)
+ if (left != this.Left || right != this.Right || isUsed != this.IsUsed || type != this.Type)
{
- var result = new BoundDeconstructionAssignmentOperator(this.Syntax, left, right, type, this.HasErrors);
+ var result = new BoundDeconstructionAssignmentOperator(this.Syntax, left, right, isUsed, type, this.HasErrors);
result.WasCompilerGenerated = this.WasCompilerGenerated;
return result;
}
@@ -8509,7 +8512,7 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
BoundTupleExpression left = (BoundTupleExpression)this.Visit(node.Left);
BoundConversion right = (BoundConversion)this.Visit(node.Right);
TypeSymbol type = this.VisitType(node.Type);
- return node.Update(left, right, type);
+ return node.Update(left, right, node.IsUsed, type);
}
public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node)
{
@@ -9503,6 +9506,7 @@ public override TreeDumperNode VisitDeconstructionAssignmentOperator(BoundDecons
{
new TreeDumperNode("left", null, new TreeDumperNode[] { Visit(node.Left, null) }),
new TreeDumperNode("right", null, new TreeDumperNode[] { Visit(node.Right, null) }),
+ new TreeDumperNode("isUsed", node.IsUsed, null),
new TreeDumperNode("type", node.Type, null)
}
);
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
index c3c9afd43013d..e356adfc610c0 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
@@ -180,12 +180,19 @@ private BoundExpression VisitExpressionImpl(BoundExpression node)
// like compound assignment does (extra flag only passed when it is an expression
// statement means that this constraint is not violated).
// Dynamic type will be erased in emit phase. It is considered equivalent to Object in lowered bound trees.
+ // Unused deconstructions are lowered to produce a return value that isn't a tuple type.
Debug.Assert(visited == null || visited.HasErrors || ReferenceEquals(visited.Type, node.Type) ||
- visited.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames));
+ visited.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames) ||
+ IsUnusedDeconstruction(node));
return visited;
}
+ private static bool IsUnusedDeconstruction(BoundExpression node)
+ {
+ return node.Kind == BoundKind.DeconstructionAssignmentOperator && !((BoundDeconstructionAssignmentOperator)node).IsUsed;
+ }
+
public override BoundNode VisitLambda(BoundLambda node)
{
_sawLambdas = true;
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
index a97ae93473c70..09cd53faba644 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs
@@ -17,7 +17,7 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
var right = node.Right;
Debug.Assert(right.Conversion.Kind == ConversionKind.Deconstruction);
- return RewriteDeconstruction(node.Left, right.Conversion, right.Operand);
+ return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed);
}
///
@@ -32,20 +32,43 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
/// - the conversion phase
/// - the assignment phase
///
- private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right)
+ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed)
{
var temps = ArrayBuilder.GetInstance();
var effects = DeconstructionSideEffects.GetInstance();
ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, temps, effects.init);
- BoundExpression returnTuple = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, inInit: true);
- if (!returnTuple.HasErrors)
+ BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true);
+ effects.Consolidate();
+
+ BoundExpression result;
+ if (!isUsed)
{
- returnTuple = VisitExpression(returnTuple);
+ // When a deconstruction is not used, the last effect is used as return value
+ Debug.Assert(returnValue is null);
+ var last = effects.PopLast();
+ if (last is null)
+ {
+ // Deconstructions with no effects lower to nothing. For example, `(_, _) = (1, 2);`
+ result = null;
+ temps.Free();
+ _ = effects.ToImmutableAndFree();
+ }
+ else
+ {
+ result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), last);
+ }
+ }
+ else
+ {
+ if (!returnValue.HasErrors)
+ {
+ returnValue = VisitExpression(returnValue);
+ }
+ result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue);
}
- BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnTuple);
- Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);
+ Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);
return result;
}
@@ -57,9 +80,9 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
/// invocation, subsequent side-effects from the right go into the deconstructions bucket (otherwise they would
/// be evaluated out of order).
///
- private BoundTupleLiteral ApplyDeconstructionConversion(ArrayBuilder leftTargets,
+ private BoundExpression ApplyDeconstructionConversion(ArrayBuilder leftTargets,
BoundExpression right, Conversion conversion, ArrayBuilder temps, DeconstructionSideEffects effects,
- bool inInit)
+ bool isUsed, bool inInit)
{
Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);
ImmutableArray rightParts = GetRightParts(right, conversion, ref temps, effects, ref inInit);
@@ -68,14 +91,14 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
Debug.Assert(!underlyingConversions.IsDefault);
Debug.Assert(leftTargets.Count == rightParts.Length && leftTargets.Count == conversion.UnderlyingConversions.Length);
- var builder = ArrayBuilder.GetInstance(leftTargets.Count);
+ var builder = isUsed ? ArrayBuilder.GetInstance(leftTargets.Count) : null;
for (int i = 0; i < leftTargets.Count; i++)
{
BoundExpression resultPart;
if (leftTargets[i].HasNestedVariables)
{
resultPart = ApplyDeconstructionConversion(leftTargets[i].NestedVariables, rightParts[i],
- underlyingConversions[i], temps, effects, inInit);
+ underlyingConversions[i], temps, effects, isUsed, inInit);
}
else
{
@@ -95,14 +118,22 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers
used: true, isChecked: false, isCompoundAssignment: false));
}
}
- builder.Add(resultPart);
+ builder?.Add(resultPart);
}
- var tupleType = TupleTypeSymbol.Create(locationOpt: null, elementTypes: builder.SelectAsArray(e => e.Type),
- elementLocations: default(ImmutableArray), elementNames: default(ImmutableArray),
- compilation: _compilation, shouldCheckConstraints: false, errorPositions: default(ImmutableArray));
+ if (isUsed)
+ {
+ var tupleType = TupleTypeSymbol.Create(locationOpt: null, elementTypes: builder.SelectAsArray(e => e.Type),
+ elementLocations: default, elementNames: default,
+ compilation: _compilation, shouldCheckConstraints: false, errorPositions: default);
- return new BoundTupleLiteral(right.Syntax, default(ImmutableArray), default(ImmutableArray), builder.ToImmutableAndFree(), tupleType);
+ return new BoundTupleLiteral(right.Syntax, argumentNamesOpt: default, inferredNamesOpt: default,
+ arguments: builder.ToImmutableAndFree(), type: tupleType);
+ }
+ else
+ {
+ return null;
+ }
}
private ImmutableArray GetRightParts(BoundExpression right, Conversion conversion,
@@ -320,7 +351,7 @@ internal static DeconstructionSideEffects GetInstance()
return result;
}
- internal ImmutableArray ToImmutableAndFree()
+ internal void Consolidate()
{
init.AddRange(deconstructions);
init.AddRange(conversions);
@@ -329,7 +360,23 @@ internal ImmutableArray ToImmutableAndFree()
deconstructions.Free();
conversions.Free();
assignments.Free();
+ }
+
+ internal BoundExpression PopLast()
+ {
+ if (init.Count == 0)
+ {
+ return null;
+ }
+ var last = init.Last();
+ init.RemoveLast();
+ return last;
+ }
+
+ // This can only be called after Consolidate
+ internal ImmutableArray ToImmutableAndFree()
+ {
return init.ToImmutableAndFree();
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
index f637c48fca2e3..273dde6228872 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs
@@ -110,7 +110,7 @@ internal sealed class TupleTypeSymbol : WrappedNamedTypeSymbol
}
var constructedType = Create(underlyingType, elementNames, errorPositions, locationOpt, elementLocations);
- if (shouldCheckConstraints)
+ if (shouldCheckConstraints && diagnostics != null)
{
constructedType.CheckConstraints(compilation.Conversions, syntax, elementLocations, compilation, diagnostics);
}
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
index 462a6eb8295d7..25652e893ead6 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
@@ -104,30 +104,25 @@ public void Deconstruct(out int a, out string b)
comp.VerifyDiagnostics();
comp.VerifyIL("C.Main", @"
{
- // Code size 44 (0x2c)
+ // Code size 40 (0x28)
.maxstack 3
- .locals init (long V_0, //x
- string V_1, //y
- int V_2,
- string V_3)
+ .locals init (string V_0, //y
+ int V_1,
+ string V_2)
IL_0000: newobj ""C..ctor()""
- IL_0005: ldloca.s V_2
- IL_0007: ldloca.s V_3
+ IL_0005: ldloca.s V_1
+ IL_0007: ldloca.s V_2
IL_0009: callvirt ""void C.Deconstruct(out int, out string)""
- IL_000e: ldloc.2
+ IL_000e: ldloc.1
IL_000f: conv.i8
- IL_0010: dup
+ IL_0010: ldloc.2
IL_0011: stloc.0
- IL_0012: ldloc.3
- IL_0013: stloc.1
- IL_0014: pop
- IL_0015: ldloc.0
- IL_0016: box ""long""
- IL_001b: ldstr "" ""
- IL_0020: ldloc.1
- IL_0021: call ""string string.Concat(object, object, object)""
- IL_0026: call ""void System.Console.WriteLine(string)""
- IL_002b: ret
+ 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
}");
}
@@ -1094,6 +1089,172 @@ public void Deconstruct(out int a, out int b)
comp.VerifyDiagnostics();
}
+ [Fact]
+ [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")]
+ public void ValueTupleNotRequiredIfReturnIsNotUsed()
+ {
+ string source = @"
+class C
+{
+ public static void Main()
+ {
+ int x, y;
+ (x, y) = new C();
+ System.Console.Write($""assignment: {x} {y}. "");
+
+ foreach (var (a, b) in new[] { new C() })
+ {
+ System.Console.Write($""foreach: {a} {b}."");
+ }
+ }
+
+ public void Deconstruct(out int a, out int b)
+ {
+ a = 1;
+ b = 2;
+ }
+}
+";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7, options: TestOptions.DebugExe);
+ comp.VerifyEmitDiagnostics();
+
+ CompileAndVerify(comp, expectedOutput: "assignment: 1 2. foreach: 1 2.");
+
+ var tree = comp.SyntaxTrees.First();
+ var model = comp.GetSemanticModel(tree);
+ var nodes = tree.GetCompilationUnitRoot().DescendantNodes();
+
+ var xy = nodes.OfType().Single();
+ Assert.Equal("(x, y)", xy.ToString());
+ var tuple1 = (TypeSymbol)model.GetTypeInfo(xy).Type;
+ Assert.Equal("(System.Int32 x, System.Int32 y)", tuple1.ToTestDisplayString());
+ var underlying1 = tuple1.TupleUnderlyingType;
+ Assert.Equal("System.ValueTuple[missing]", underlying1.ToTestDisplayString());
+
+ var ab = nodes.OfType().Single();
+ var tuple2 = (TypeSymbol)model.GetTypeInfo(ab).Type;
+ Assert.Equal("(System.Int32 a, System.Int32 b)", tuple2.ToTestDisplayString());
+ var underlying2 = tuple2.TupleUnderlyingType;
+ Assert.Equal("System.ValueTuple[missing]", underlying2.ToTestDisplayString());
+ }
+
+ [Fact]
+ [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")]
+ public void ValueTupleNotRequiredIfReturnIsNotUsed2()
+ {
+ string source = @"
+class C
+{
+ public static void Main()
+ {
+ int x, y;
+ for((x, y) = new C(1); ; (x, y) = new C(2))
+ {
+ }
+ }
+
+ public C(int c) { }
+ public void Deconstruct(out int a, out int b)
+ {
+ a = 1;
+ b = 2;
+ }
+}
+";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7, options: TestOptions.DebugExe);
+ comp.VerifyEmitDiagnostics();
+
+ var tree = comp.SyntaxTrees.First();
+ var model = comp.GetSemanticModel(tree);
+ var nodes = tree.GetCompilationUnitRoot().DescendantNodes();
+
+ var tuple1 = nodes.OfType().ElementAt(0);
+ Assert.Equal("(x, y) = new C(1)", tuple1.Parent.ToString());
+ var tupleType1 = (TypeSymbol)model.GetTypeInfo(tuple1).Type;
+ Assert.Equal("(System.Int32 x, System.Int32 y)", tupleType1.ToTestDisplayString());
+ var underlying1 = tupleType1.TupleUnderlyingType;
+ Assert.Equal("System.ValueTuple[missing]", underlying1.ToTestDisplayString());
+
+ var tuple2 = nodes.OfType().ElementAt(1);
+ Assert.Equal("(x, y) = new C(2)", tuple2.Parent.ToString());
+ var tupleType2 = (TypeSymbol)model.GetTypeInfo(tuple1).Type;
+ Assert.Equal("(System.Int32 x, System.Int32 y)", tupleType2.ToTestDisplayString());
+ var underlying2 = tupleType1.TupleUnderlyingType;
+ Assert.Equal("System.ValueTuple[missing]", underlying2.ToTestDisplayString());
+ }
+
+ [Fact]
+ [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")]
+ public void ValueTupleNotRequiredIfReturnIsNotUsed3()
+ {
+ string source = @"
+class C
+{
+ public static void Main()
+ {
+ int x, y;
+ (x, y) = new C();
+ }
+
+ public C() { }
+ public void Deconstruct(out int a, out int b)
+ {
+ a = 1;
+ b = 2;
+ }
+}
+namespace System
+{
+ [Obsolete]
+ public struct ValueTuple
+ {
+ [Obsolete]
+ public T1 Item1;
+
+ [Obsolete]
+ public T2 Item2;
+
+ public ValueTuple(T1 item1, T2 item2) { Item1 = item1; Item2 = item2; }
+ }
+}
+";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7);
+ comp.VerifyEmitDiagnostics();
+
+ var tree = comp.SyntaxTrees.First();
+ var model = comp.GetSemanticModel(tree);
+ var nodes = tree.GetCompilationUnitRoot().DescendantNodes();
+
+ var tuple = nodes.OfType().ElementAt(0);
+ Assert.Equal("(x, y) = new C()", tuple.Parent.ToString());
+ var tupleType = (TypeSymbol)model.GetTypeInfo(tuple).Type;
+ Assert.Equal("(System.Int32 x, System.Int32 y)", tupleType.ToTestDisplayString());
+ var underlying = tupleType.TupleUnderlyingType;
+ Assert.Equal("System.ValueTuple", underlying.ToTestDisplayString());
+ }
+
+ [Fact]
+ [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")]
+ public void ValueTupleRequiredWhenRightHandSideIsTuple()
+ {
+ string source = @"
+class C
+{
+ public static void Main()
+ {
+ int x, y;
+ (x, y) = (1, 2);
+ }
+}
+";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7);
+ comp.VerifyDiagnostics(
+ // (7,18): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is ambiguous (imported twice)
+ // (x, y) = (1, 2);
+ Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(1, 2)").WithArguments("System.ValueTuple`2").WithLocation(7, 18)
+ );
+ }
+
[Fact]
public void ValueTupleReturnMissingMemberWithCSharp7()
{
@@ -1197,9 +1358,9 @@ class C
{
public void M()
{
- (int x, var (err1, y)) = (0, new C());
- (ArgIterator err2, var err3) = M2();
- foreach ((ArgIterator err4, var err5) in new[] { M2() })
+ (int x, var (err1, y)) = (0, new C()); // ok, no return value used
+ (ArgIterator err2, var err3) = M2(); // ok, no return value
+ foreach ((ArgIterator err4, var err5) in new[] { M2() }) // ok, no return value
{
}
}
@@ -1228,21 +1389,6 @@ public void Deconstruct(out ArgIterator a, out int b)
// (14,46): error CS0306: The type 'ArgIterator' may not be used as a type argument
// public static (ArgIterator, ArgIterator) M2()
Diagnostic(ErrorCode.ERR_BadTypeArgument, "M2").WithArguments("System.ArgIterator").WithLocation(14, 46),
- // (7,22): error CS0306: The type 'ArgIterator' may not be used as a type argument
- // (int x, var (err1, y)) = (0, new C());
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "err1").WithArguments("System.ArgIterator").WithLocation(7, 22),
- // (8,10): error CS0306: The type 'ArgIterator' may not be used as a type argument
- // (ArgIterator err2, var err3) = M2();
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "ArgIterator err2").WithArguments("System.ArgIterator").WithLocation(8, 10),
- // (8,28): error CS0306: The type 'ArgIterator' may not be used as a type argument
- // (ArgIterator err2, var err3) = M2();
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err3").WithArguments("System.ArgIterator").WithLocation(8, 28),
- // (9,19): error CS0306: The type 'ArgIterator' may not be used as a type argument
- // foreach ((ArgIterator err4, var err5) in new[] { M2() })
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "ArgIterator err4").WithArguments("System.ArgIterator").WithLocation(9, 19),
- // (9,37): error CS0306: The type 'ArgIterator' may not be used as a type argument
- // foreach ((ArgIterator err4, var err5) in new[] { M2() })
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err5").WithArguments("System.ArgIterator").WithLocation(9, 37),
// (16,17): error CS0306: The type 'ArgIterator' may not be used as a type argument
// return (default(ArgIterator), default(ArgIterator));
Diagnostic(ErrorCode.ERR_BadTypeArgument, "default(ArgIterator)").WithArguments("System.ArgIterator").WithLocation(16, 17),
@@ -1260,9 +1406,9 @@ unsafe class C
{
public void M()
{
- (int x, var (err1, y)) = (0, new C());
- (var err2, var err3) = M2();
- foreach ((var err4, var err5) in new[] { M2() })
+ (int x, var (err1, y)) = (0, new C()); // ok, no return value
+ (var err2, var err3) = M2(); // ok, no return value
+ foreach ((var err4, var err5) in new[] { M2() }) // ok, no return value
{
}
}
@@ -1288,21 +1434,6 @@ public void Deconstruct(out int* a, out int b)
// (13,32): error CS0306: The type 'int*' may not be used as a type argument
// public static (int*, int*) M2()
Diagnostic(ErrorCode.ERR_BadTypeArgument, "M2").WithArguments("int*").WithLocation(13, 32),
- // (6,22): error CS0306: The type 'int*' may not be used as a type argument
- // (int x, var (err1, y)) = (0, new C());
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "err1").WithArguments("int*").WithLocation(6, 22),
- // (7,10): error CS0306: The type 'int*' may not be used as a type argument
- // (var err2, var err3) = M2();
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err2").WithArguments("int*").WithLocation(7, 10),
- // (7,20): error CS0306: The type 'int*' may not be used as a type argument
- // (var err2, var err3) = M2();
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err3").WithArguments("int*").WithLocation(7, 20),
- // (8,19): error CS0306: The type 'int*' may not be used as a type argument
- // foreach ((var err4, var err5) in new[] { M2() })
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err4").WithArguments("int*").WithLocation(8, 19),
- // (8,29): error CS0306: The type 'int*' may not be used as a type argument
- // foreach ((var err4, var err5) in new[] { M2() })
- Diagnostic(ErrorCode.ERR_BadTypeArgument, "var err5").WithArguments("int*").WithLocation(8, 29),
// (15,17): error CS0306: The type 'int*' may not be used as a type argument
// return (default(int*), default(int*));
Diagnostic(ErrorCode.ERR_BadTypeArgument, "default(int*)").WithArguments("int*").WithLocation(15, 17),
@@ -1312,6 +1443,53 @@ public void Deconstruct(out int* a, out int b)
);
}
+ [Fact]
+ public void Constraints_03()
+ {
+ string source = @"
+unsafe class C
+{
+ public void M()
+ {
+ int ok;
+ int* err1, err2;
+ var t = ((ok, (err1, ok)) = (0, new C()));
+ var t2 = ((err1, err2) = M2());
+ }
+
+ public static (int*, int*) M2()
+ {
+ throw null;
+ }
+
+ public void Deconstruct(out int* a, out int b)
+ {
+ a = default(int*);
+ b = 2;
+ }
+}
+";
+
+ var comp = CreateStandardCompilation(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, options: TestOptions.UnsafeDebugDll);
+ comp.VerifyDiagnostics(
+ // (12,32): error CS0306: The type 'int*' may not be used as a type argument
+ // public static (int*, int*) M2()
+ Diagnostic(ErrorCode.ERR_BadTypeArgument, "M2").WithArguments("int*").WithLocation(12, 32),
+ // (12,32): error CS0306: The type 'int*' may not be used as a type argument
+ // public static (int*, int*) M2()
+ Diagnostic(ErrorCode.ERR_BadTypeArgument, "M2").WithArguments("int*").WithLocation(12, 32),
+ // (8,24): error CS0306: The type 'int*' may not be used as a type argument
+ // var t = ((ok, (err1, ok)) = (0, new C()));
+ Diagnostic(ErrorCode.ERR_BadTypeArgument, "err1").WithArguments("int*").WithLocation(8, 24),
+ // (9,20): error CS0306: The type 'int*' may not be used as a type argument
+ // var t2 = ((err1, err2) = M2());
+ Diagnostic(ErrorCode.ERR_BadTypeArgument, "err1").WithArguments("int*").WithLocation(9, 20),
+ // (9,26): error CS0306: The type 'int*' may not be used as a type argument
+ // var t2 = ((err1, err2) = M2());
+ Diagnostic(ErrorCode.ERR_BadTypeArgument, "err2").WithArguments("int*").WithLocation(9, 26)
+ );
+ }
+
[Fact]
public void MixedDeconstructionCannotBeParsed()
{
@@ -1662,18 +1840,16 @@ static void Swap()
comp.VerifyDiagnostics();
comp.VerifyIL("C.Swap", @"
{
- // Code size 25 (0x19)
+ // Code size 23 (0x17)
.maxstack 2
.locals init (int V_0)
IL_0000: ldsfld ""int C.y""
IL_0005: ldsfld ""int C.x""
IL_000a: stloc.0
- IL_000b: dup
- IL_000c: stsfld ""int C.x""
- IL_0011: ldloc.0
- IL_0012: stsfld ""int C.y""
- IL_0017: pop
- IL_0018: ret
+ IL_000b: stsfld ""int C.x""
+ IL_0010: ldloc.0
+ IL_0011: stsfld ""int C.y""
+ IL_0016: ret
}
");
}
@@ -3341,8 +3517,8 @@ static void Main()
// Code size 70 (0x46)
.maxstack 2
.locals init (System.Collections.Generic.IEnumerator<(int, int)> V_0,
- int V_1, //x2
- System.ValueTuple V_2)
+ 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
@@ -3351,14 +3527,14 @@ .maxstack 2
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: stloc.2
- IL_0014: ldloc.2
- IL_0015: ldfld ""int System.ValueTuple.Item1""
- IL_001a: ldloc.2
- IL_001b: ldfld ""int System.ValueTuple.Item2""
- IL_0020: stloc.1
+ 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.1
+ IL_0026: ldloc.2
IL_0027: box ""int""
IL_002c: call ""void C.Print(object, object)""
IL_0031: ldloc.0
@@ -3422,60 +3598,57 @@ static void Main()
comp.VerifyDiagnostics();
comp.VerifyIL("C.Main",
@"{
- // Code size 96 (0x60)
+ // Code size 91 (0x5b)
.maxstack 4
.locals init ((int, int)[] V_0,
int V_1,
int V_2, //x1
- int V_3, //x2
- System.ValueTuple V_4)
+ 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_0059
+ IL_0008: br.s IL_0054
IL_000a: ldloc.0
IL_000b: ldloc.1
IL_000c: ldelem ""System.ValueTuple""
- IL_0011: stloc.s V_4
- IL_0013: ldloc.s V_4
- IL_0015: ldfld ""int System.ValueTuple.Item1""
- IL_001a: stloc.2
- IL_001b: ldloc.s V_4
- IL_001d: ldfld ""int System.ValueTuple.Item2""
- IL_0022: stloc.3
- IL_0023: ldc.i4.4
- IL_0024: newarr ""object""
- IL_0029: dup
- IL_002a: ldc.i4.0
- IL_002b: ldloc.2
- IL_002c: box ""int""
- IL_0031: stelem.ref
- IL_0032: dup
- IL_0033: ldc.i4.1
- IL_0034: ldstr "" ""
- IL_0039: stelem.ref
- IL_003a: dup
- IL_003b: ldc.i4.2
- IL_003c: ldloc.3
- IL_003d: box ""int""
- IL_0042: stelem.ref
- IL_0043: dup
- IL_0044: ldc.i4.3
- IL_0045: ldstr "" - ""
- IL_004a: stelem.ref
- IL_004b: call ""string string.Concat(params object[])""
- IL_0050: call ""void System.Console.Write(string)""
- IL_0055: ldloc.1
- IL_0056: ldc.i4.1
- IL_0057: add
- IL_0058: stloc.1
- IL_0059: ldloc.1
- IL_005a: ldloc.0
- IL_005b: ldlen
- IL_005c: conv.i4
- IL_005d: blt.s IL_000a
- IL_005f: ret
+ 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
}");
}
@@ -3522,15 +3695,15 @@ static void Main()
comp.VerifyDiagnostics();
comp.VerifyIL("C.Main",
@"{
- // Code size 107 (0x6b)
+ // 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, //x2
- System.ValueTuple V_6)
+ int V_5, //x1
+ int V_6) //x2
IL_0000: call ""(int, int)[,] C.M()""
IL_0005: stloc.0
IL_0006: ldloc.0
@@ -3545,41 +3718,41 @@ .maxstack 3
IL_0017: ldc.i4.0
IL_0018: callvirt ""int System.Array.GetLowerBound(int)""
IL_001d: stloc.3
- IL_001e: br.s IL_0066
+ 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_005d
+ 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: stloc.s V_6
- IL_0036: ldloc.s V_6
- IL_0038: ldfld ""int System.ValueTuple.Item1""
- IL_003d: ldloc.s V_6
- IL_003f: ldfld ""int System.ValueTuple.Item2""
- IL_0044: stloc.s V_5
- IL_0046: box ""int""
- IL_004b: ldloc.s V_5
- IL_004d: box ""int""
- IL_0052: call ""void C.Print(object, object)""
- IL_0057: ldloc.s V_4
- IL_0059: ldc.i4.1
- IL_005a: add
- IL_005b: stloc.s V_4
- IL_005d: ldloc.s V_4
- IL_005f: ldloc.2
- IL_0060: ble.s IL_002b
- IL_0062: ldloc.3
- IL_0063: ldc.i4.1
- IL_0064: add
- IL_0065: stloc.3
- IL_0066: ldloc.3
- IL_0067: ldloc.1
- IL_0068: ble.s IL_0020
- IL_006a: ret
+ 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
}");
}
@@ -3880,61 +4053,56 @@ static void Main()
comp.VerifyIL("C.Main",
@"{
- // Code size 95 (0x5f)
+ // Code size 90 (0x5a)
.maxstack 3
.locals init (System.Collections.Generic.IEnumerator>> V_0,
- long V_1, //x1
- int V_2, //x2
- int V_3, //x3
- int V_4,
- Pair V_5,
- int V_6,
- int V_7)
+ 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_004a
+ 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_4
- IL_0015: ldloca.s V_5
+ 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_5
- IL_001e: ldloca.s V_6
- IL_0020: ldloca.s V_7
+ 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.s V_4
- IL_0029: conv.i8
- IL_002a: dup
+ 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: ldloc.s V_7
- IL_0031: stloc.3
- IL_0032: pop
- IL_0033: ldloc.1
- IL_0034: box ""long""
- IL_0039: ldloc.2
- IL_003a: box ""int""
- IL_003f: ldloc.3
- IL_0040: box ""int""
- IL_0045: call ""void C.Print(object, object, object)""
- IL_004a: ldloc.0
- IL_004b: callvirt ""bool System.Collections.IEnumerator.MoveNext()""
- IL_0050: brtrue.s IL_000d
- IL_0052: leave.s IL_005e
+ 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_0054: ldloc.0
- IL_0055: brfalse.s IL_005d
- IL_0057: ldloc.0
- IL_0058: callvirt ""void System.IDisposable.Dispose()""
- IL_005d: endfinally
+ 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_005e: ret
+ IL_0059: ret
}
");
}
@@ -5614,7 +5782,11 @@ public void VerifyDiscardIL()
@"
class C
{
- static int M()
+ C()
+ {
+ System.Console.Write(""ctor"");
+ }
+ static int Main()
{
var (x, _, _) = (1, new C(), 2);
return x;
@@ -5622,15 +5794,14 @@ static int M()
}
";
- var comp = CompileAndVerify(source, additionalRefs: s_valueTupleRefs);
+ var comp = CompileAndVerify(source, expectedOutput: "ctor", additionalRefs: s_valueTupleRefs);
comp.VerifyDiagnostics();
- comp.VerifyIL("C.M()", @"
+ comp.VerifyIL("C.Main()", @"
{
// Code size 8 (0x8)
.maxstack 1
- .locals init (C V_0)
IL_0000: newobj ""C..ctor()""
- IL_0005: stloc.0
+ IL_0005: pop
IL_0006: ldc.i4.1
IL_0007: ret
}");
@@ -5932,7 +6103,6 @@ static void Main()
);
}
-
[Fact]
public void SimpleDiscardDeconstructInScript()
{
@@ -5972,6 +6142,26 @@ public void SimpleDiscardDeconstructInScript()
CompileAndVerify(comp, sourceSymbolValidator: validator);
}
+ [Fact]
+ public void SimpleDiscardDeconstructInScript2()
+ {
+ var source =
+@"
+public class C
+{
+ public C() { System.Console.Write(""ctor""); }
+ public void Deconstruct(out string x, out string y) { x = y = null; }
+}
+(string _, string _) = new C();
+";
+
+
+ var comp = CreateCompilationWithMscorlib45(source, parseOptions: TestOptions.Script, options: TestOptions.DebugExe);
+
+ comp.VerifyDiagnostics();
+ CompileAndVerify(comp, expectedOutput: "ctor");
+ }
+
[Fact]
public void SingleDiscardInAssignmentInScript()
{
@@ -7245,6 +7435,87 @@ public static void Deconstruct(this object input, out object output1, out object
);
}
+ [Fact]
+ public void TestDeconstructOnErrorType()
+ {
+ var source =
+@"
+class C
+{
+ Error M()
+ {
+ int x, y;
+ (x, y) = M();
+ throw null;
+ }
+}";
+
+ var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugDll); // no ValueTuple reference
+ comp.VerifyDiagnostics(
+ // (4,5): error CS0246: The type or namespace name 'Error' could not be found (are you missing a using directive or an assembly reference?)
+ // Error M()
+ Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Error").WithArguments("Error").WithLocation(4, 5)
+ );
+ }
+
+ [Fact]
+ public void TestDeconstructOnErrorTypeFromImageReference()
+ {
+ var missing_cs = "public class Missing { }";
+ var missing = CreateCompilationWithMscorlib45(missing_cs, options: TestOptions.DebugDll, assemblyName: "missing");
+
+ var lib_cs = "public class C { public Missing M() { throw null; } }";
+ var lib = CreateCompilationWithMscorlib45(lib_cs, references: new[] { missing.EmitToImageReference() }, options: TestOptions.DebugDll);
+
+ var source =
+@"
+class D
+{
+ void M()
+ {
+ int x, y;
+ (x, y) = new C().M();
+ throw null;
+ }
+}";
+
+ var comp = CreateCompilationWithMscorlib45(source, references: new[] { lib.EmitToImageReference() }, options: TestOptions.DebugDll); // no ValueTuple reference
+ comp.VerifyDiagnostics(
+ // (7,18): error CS0012: The type 'Missing' is defined in an assembly that is not referenced. You must add a reference to assembly 'missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
+ // (x, y) = new C().M();
+ Diagnostic(ErrorCode.ERR_NoTypeDef, "new C().M").WithArguments("Missing", "missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 18)
+ );
+ }
+
+ [Fact]
+ public void TestDeconstructOnErrorTypeFromCompilationReference()
+ {
+ var missing_cs = "public class Missing { }";
+ var missing = CreateCompilationWithMscorlib45(missing_cs, options: TestOptions.DebugDll, assemblyName: "missing");
+
+ var lib_cs = "public class C { public Missing M() { throw null; } }";
+ var lib = CreateCompilationWithMscorlib45(lib_cs, references: new[] { missing.ToMetadataReference() }, options: TestOptions.DebugDll);
+
+ var source =
+@"
+class D
+{
+ void M()
+ {
+ int x, y;
+ (x, y) = new C().M();
+ throw null;
+ }
+}";
+
+ var comp = CreateCompilationWithMscorlib45(source, references: new[] { lib.ToMetadataReference() }, options: TestOptions.DebugDll); // no ValueTuple reference
+ comp.VerifyDiagnostics(
+ // (7,18): error CS0012: The type 'Missing' is defined in an assembly that is not referenced. You must add a reference to assembly 'missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
+ // (x, y) = new C().M();
+ Diagnostic(ErrorCode.ERR_NoTypeDef, "new C().M").WithArguments("Missing", "missing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 18)
+ );
+ }
+
[Fact, WorkItem(17756, "https://github.com/dotnet/roslyn/issues/17756")]
public void TestDiscardedAssignmentNotLvalue()
{
@@ -7366,5 +7637,70 @@ public static explicit operator (byte, byte)(C c)
}";
CompileAndVerify(source, expectedOutput: @"3 4", additionalRefs: s_valueTupleRefs);
}
+
+ [Fact, WorkItem(19398, "https://github.com/dotnet/roslyn/issues/19398")]
+ public void DeconstructionLoweredToNothing()
+ {
+ var source = @"
+class C
+{
+ static void M()
+ {
+ for (var(_, _) = (1, 2); ; (_, _) = (3, 4))
+ {
+ }
+ }
+}";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7, references: s_valueTupleRefs);
+ comp.VerifyDiagnostics();
+ var verifier = CompileAndVerify(comp);
+ verifier.VerifyIL("C.M", @"
+{
+ // Code size 2 (0x2)
+ .maxstack 0
+ IL_0000: br.s IL_0000
+}");
+ }
+
+ [Fact, WorkItem(19398, "https://github.com/dotnet/roslyn/issues/19398")]
+ public void DeconstructionLoweredToNothing2()
+ {
+ var source = @"
+class C
+{
+ static void M()
+ {
+ (_, _) = (1, 2);
+ }
+}";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7, references: s_valueTupleRefs);
+ comp.VerifyDiagnostics();
+ var verifier = CompileAndVerify(comp);
+ verifier.VerifyIL("C.M", @"
+{
+ // Code size 1 (0x1)
+ .maxstack 0
+ IL_0000: ret
+}");
+ }
+
+ [Fact, WorkItem(19398, "https://github.com/dotnet/roslyn/issues/19398")]
+ public void DeconstructionLoweredToNothing3()
+ {
+ var source = @"
+class C
+{
+ static void Main()
+ {
+ foreach (var(_, _) in new[] { (1, 2) })
+ {
+ System.Console.Write(""once"");
+ }
+ }
+}";
+ var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular7, references: s_valueTupleRefs, options: TestOptions.DebugExe);
+ comp.VerifyDiagnostics();
+ CompileAndVerify(comp, expectedOutput: "once");
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs
index 937739bd4813d..a5cf7864c599b 100644
--- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs
@@ -8197,7 +8197,7 @@ public static void G()
var v0 = CompileAndVerify(compilation0);
v0.VerifyIL("C.G", @"
{
- // Code size 72 (0x48)
+ // Code size 70 (0x46)
.maxstack 2
.locals init ((int, (bool, double))[] V_0,
int V_1,
@@ -8211,38 +8211,36 @@ .maxstack 2
IL_0007: stloc.0
IL_0008: ldc.i4.0
IL_0009: stloc.1
- IL_000a: br.s IL_0041
+ IL_000a: br.s IL_003f
IL_000c: ldloc.0
IL_000d: ldloc.1
IL_000e: ldelem ""System.ValueTuple""
IL_0013: dup
IL_0014: ldfld ""(bool, double) System.ValueTuple.Item2""
IL_0019: stloc.s V_5
- IL_001b: dup
- IL_001c: ldfld ""int System.ValueTuple.Item1""
- IL_0021: stloc.2
- IL_0022: ldloc.s V_5
- IL_0024: ldfld ""bool System.ValueTuple.Item1""
- IL_0029: stloc.3
- IL_002a: ldloc.s V_5
- IL_002c: ldfld ""double System.ValueTuple.Item2""
- IL_0031: stloc.s V_4
- IL_0033: pop
- IL_0034: nop
- IL_0035: ldloc.2
- IL_0036: call ""void System.Console.WriteLine(int)""
- IL_003b: nop
- IL_003c: nop
- IL_003d: ldloc.1
- IL_003e: ldc.i4.1
- IL_003f: add
- IL_0040: stloc.1
- IL_0041: ldloc.1
- IL_0042: ldloc.0
- IL_0043: ldlen
- IL_0044: conv.i4
- IL_0045: blt.s IL_000c
- IL_0047: ret
+ IL_001b: ldfld ""int System.ValueTuple.Item1""
+ IL_0020: stloc.2
+ IL_0021: ldloc.s V_5
+ IL_0023: ldfld ""bool System.ValueTuple.Item1""
+ IL_0028: stloc.3
+ IL_0029: ldloc.s V_5
+ IL_002b: ldfld ""double System.ValueTuple.Item2""
+ IL_0030: stloc.s V_4
+ IL_0032: nop
+ IL_0033: ldloc.2
+ IL_0034: call ""void System.Console.WriteLine(int)""
+ IL_0039: nop
+ IL_003a: nop
+ IL_003b: ldloc.1
+ IL_003c: ldc.i4.1
+ IL_003d: add
+ IL_003e: stloc.1
+ IL_003f: ldloc.1
+ IL_0040: ldloc.0
+ IL_0041: ldlen
+ IL_0042: conv.i4
+ IL_0043: blt.s IL_000c
+ IL_0045: ret
}
");
@@ -8256,7 +8254,7 @@ .maxstack 2
diff1.VerifyIL("C.G", @"
{
- // Code size 80 (0x50)
+ // Code size 78 (0x4e)
.maxstack 2
.locals init ([unchanged] V_0,
[int] V_1,
@@ -8273,38 +8271,36 @@ .maxstack 2
IL_0007: stloc.s V_6
IL_0009: ldc.i4.0
IL_000a: stloc.s V_7
- IL_000c: br.s IL_0047
+ IL_000c: br.s IL_0045
IL_000e: ldloc.s V_6
IL_0010: ldloc.s V_7
IL_0012: ldelem ""System.ValueTuple""
IL_0017: dup
IL_0018: ldfld ""(bool, double) System.ValueTuple.Item2""
IL_001d: stloc.s V_8
- IL_001f: dup
- IL_0020: ldfld ""int System.ValueTuple.Item1""
- IL_0025: stloc.2
- IL_0026: ldloc.s V_8
- IL_0028: ldfld ""bool System.ValueTuple.Item1""
- IL_002d: stloc.3
- IL_002e: ldloc.s V_8
- IL_0030: ldfld ""double System.ValueTuple.Item2""
- IL_0035: stloc.s V_4
- IL_0037: pop
- IL_0038: nop
- IL_0039: ldloc.2
- IL_003a: call ""void System.Console.WriteLine(int)""
- IL_003f: nop
- IL_0040: nop
- IL_0041: ldloc.s V_7
- IL_0043: ldc.i4.1
- IL_0044: add
- IL_0045: stloc.s V_7
- IL_0047: ldloc.s V_7
- IL_0049: ldloc.s V_6
- IL_004b: ldlen
- IL_004c: conv.i4
- IL_004d: blt.s IL_000e
- IL_004f: ret
+ IL_001f: ldfld ""int System.ValueTuple.Item1""
+ IL_0024: stloc.2
+ IL_0025: ldloc.s V_8
+ IL_0027: ldfld ""bool System.ValueTuple.Item1""
+ IL_002c: stloc.3
+ IL_002d: ldloc.s V_8
+ IL_002f: ldfld ""double System.ValueTuple.Item2""
+ IL_0034: stloc.s V_4
+ IL_0036: nop
+ IL_0037: ldloc.2
+ IL_0038: call ""void System.Console.WriteLine(int)""
+ IL_003d: nop
+ IL_003e: nop
+ IL_003f: ldloc.s V_7
+ IL_0041: ldc.i4.1
+ IL_0042: add
+ IL_0043: stloc.s V_7
+ IL_0045: ldloc.s V_7
+ IL_0047: ldloc.s V_6
+ IL_0049: ldlen
+ IL_004a: conv.i4
+ IL_004b: blt.s IL_000e
+ IL_004d: ret
}
");
@@ -8315,7 +8311,7 @@ .maxstack 2
diff2.VerifyIL("C.G", @"
{
- // Code size 66 (0x42)
+ // Code size 61 (0x3d)
.maxstack 2
.locals init ([unchanged] V_0,
[int] V_1,
@@ -8328,40 +8324,37 @@ .maxstack 2
[unchanged] V_8,
(int, (bool, double))[] V_9,
int V_10,
- System.ValueTuple V_11, //yz
- System.ValueTuple V_12)
+ System.ValueTuple V_11) //yz
IL_0000: nop
IL_0001: nop
IL_0002: call ""(int, (bool, double))[] C.F()""
IL_0007: stloc.s V_9
IL_0009: ldc.i4.0
IL_000a: stloc.s V_10
- IL_000c: br.s IL_0039
+ IL_000c: br.s IL_0034
IL_000e: ldloc.s V_9
IL_0010: ldloc.s V_10
IL_0012: ldelem ""System.ValueTuple""
- IL_0017: stloc.s V_12
- IL_0019: ldloc.s V_12
- IL_001b: ldfld ""int System.ValueTuple.Item1""
- IL_0020: stloc.2
- IL_0021: ldloc.s V_12
- IL_0023: ldfld ""(bool, double) System.ValueTuple.Item2""
- IL_0028: stloc.s V_11
- IL_002a: nop
- IL_002b: ldloc.2
- IL_002c: call ""void System.Console.WriteLine(int)""
- IL_0031: nop
- IL_0032: nop
- IL_0033: ldloc.s V_10
- IL_0035: ldc.i4.1
- IL_0036: add
- IL_0037: stloc.s V_10
- IL_0039: ldloc.s V_10
- IL_003b: ldloc.s V_9
- IL_003d: ldlen
- IL_003e: conv.i4
- IL_003f: blt.s IL_000e
- IL_0041: ret
+ IL_0017: dup
+ IL_0018: ldfld ""int System.ValueTuple.Item1""
+ IL_001d: stloc.2
+ IL_001e: ldfld ""(bool, double) System.ValueTuple.Item2""
+ IL_0023: stloc.s V_11
+ IL_0025: nop
+ IL_0026: ldloc.2
+ IL_0027: call ""void System.Console.WriteLine(int)""
+ IL_002c: nop
+ IL_002d: nop
+ IL_002e: ldloc.s V_10
+ IL_0030: ldc.i4.1
+ IL_0031: add
+ IL_0032: stloc.s V_10
+ IL_0034: ldloc.s V_10
+ IL_0036: ldloc.s V_9
+ IL_0038: ldlen
+ IL_0039: conv.i4
+ IL_003a: blt.s IL_000e
+ IL_003c: ret
}
");
}
diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs
index 525e292e8a66d..7066c48eccdf2 100644
--- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs
@@ -3786,7 +3786,7 @@ public static void G()
diff1.VerifyIL("C.G", @"
{
- // Code size 80 (0x50)
+ // Code size 78 (0x4e)
.maxstack 2
.locals init ([unchanged] V_0,
[int] V_1,
@@ -3803,38 +3803,36 @@ .maxstack 2
IL_0007: stloc.s V_6
IL_0009: ldc.i4.0
IL_000a: stloc.s V_7
- ~IL_000c: br.s IL_0047
+ ~IL_000c: br.s IL_0045
-IL_000e: ldloc.s V_6
IL_0010: ldloc.s V_7
IL_0012: ldelem ""System.ValueTuple""
IL_0017: dup
IL_0018: ldfld ""(bool, double) System.ValueTuple.Item2""
IL_001d: stloc.s V_8
- IL_001f: dup
- IL_0020: ldfld ""int System.ValueTuple.Item1""
- IL_0025: stloc.2
- IL_0026: ldloc.s V_8
- IL_0028: ldfld ""bool System.ValueTuple.Item1""
- IL_002d: stloc.3
- IL_002e: ldloc.s V_8
- IL_0030: ldfld ""double System.ValueTuple.Item2""
- IL_0035: stloc.s V_4
- IL_0037: pop
- -IL_0038: nop
- -IL_0039: ldloc.2
- IL_003a: call ""void System.Console.WriteLine(int)""
- IL_003f: nop
- -IL_0040: nop
- ~IL_0041: ldloc.s V_7
- IL_0043: ldc.i4.1
- IL_0044: add
- IL_0045: stloc.s V_7
- -IL_0047: ldloc.s V_7
- IL_0049: ldloc.s V_6
- IL_004b: ldlen
- IL_004c: conv.i4
- IL_004d: blt.s IL_000e
- -IL_004f: ret
+ IL_001f: ldfld ""int System.ValueTuple.Item1""
+ IL_0024: stloc.2
+ IL_0025: ldloc.s V_8
+ IL_0027: ldfld ""bool System.ValueTuple.Item1""
+ IL_002c: stloc.3
+ IL_002d: ldloc.s V_8
+ IL_002f: ldfld ""double System.ValueTuple.Item2""
+ IL_0034: stloc.s V_4
+ -IL_0036: nop
+ -IL_0037: ldloc.2
+ IL_0038: call ""void System.Console.WriteLine(int)""
+ IL_003d: nop
+ -IL_003e: nop
+ ~IL_003f: ldloc.s V_7
+ IL_0041: ldc.i4.1
+ IL_0042: add
+ IL_0043: stloc.s V_7
+ -IL_0045: ldloc.s V_7
+ IL_0047: ldloc.s V_6
+ IL_0049: ldlen
+ IL_004a: conv.i4
+ IL_004b: blt.s IL_000e
+ -IL_004d: ret
}
", methodToken: diff1.UpdatedMethods.Single());
}
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
index a4ab3e12e3d18..bfb36a226b003 100644
--- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
@@ -2688,7 +2688,7 @@ public static void Main()
v.VerifyIL("C.Main", @"
{
- // Code size 72 (0x48)
+ // Code size 70 (0x46)
.maxstack 2
.locals init ((int, (bool, double))[] V_0,
int V_1,
@@ -2706,7 +2706,7 @@ .maxstack 2
IL_0008: ldc.i4.0
IL_0009: stloc.1
// sequence point:
- IL_000a: br.s IL_0041
+ IL_000a: br.s IL_003f
// sequence point: var (c, (d, e))
IL_000c: ldloc.0
IL_000d: ldloc.1
@@ -2714,37 +2714,35 @@ .maxstack 2
IL_0013: dup
IL_0014: ldfld ""(bool, double) System.ValueTuple.Item2""
IL_0019: stloc.s V_5
- IL_001b: dup
- IL_001c: ldfld ""int System.ValueTuple.Item1""
- IL_0021: stloc.2
- IL_0022: ldloc.s V_5
- IL_0024: ldfld ""bool System.ValueTuple.Item1""
- IL_0029: stloc.3
- IL_002a: ldloc.s V_5
- IL_002c: ldfld ""double System.ValueTuple.Item2""
- IL_0031: stloc.s V_4
- IL_0033: pop
+ IL_001b: ldfld ""int System.ValueTuple.Item1""
+ IL_0020: stloc.2
+ IL_0021: ldloc.s V_5
+ IL_0023: ldfld ""bool System.ValueTuple.Item1""
+ IL_0028: stloc.3
+ IL_0029: ldloc.s V_5
+ IL_002b: ldfld ""double System.ValueTuple.Item2""
+ IL_0030: stloc.s V_4
// sequence point: {
- IL_0034: nop
+ IL_0032: nop
// sequence point: System.Console.WriteLine(c);
- IL_0035: ldloc.2
- IL_0036: call ""void System.Console.WriteLine(int)""
- IL_003b: nop
+ IL_0033: ldloc.2
+ IL_0034: call ""void System.Console.WriteLine(int)""
+ IL_0039: nop
// sequence point: }
- IL_003c: nop
+ IL_003a: nop
// sequence point:
- IL_003d: ldloc.1
- IL_003e: ldc.i4.1
- IL_003f: add
- IL_0040: stloc.1
+ IL_003b: ldloc.1
+ IL_003c: ldc.i4.1
+ IL_003d: add
+ IL_003e: stloc.1
// sequence point: in
- IL_0041: ldloc.1
- IL_0042: ldloc.0
- IL_0043: ldlen
- IL_0044: conv.i4
- IL_0045: blt.s IL_000c
+ IL_003f: ldloc.1
+ IL_0040: ldloc.0
+ IL_0041: ldlen
+ IL_0042: conv.i4
+ IL_0043: blt.s IL_000c
// sequence point: }
- IL_0047: ret
+ IL_0045: ret
}
", sequencePoints: "C.Main", source: source);
@@ -2779,18 +2777,18 @@ .maxstack 2
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs
index a394b9160e9b6..219f1803a6fbf 100644
--- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs
@@ -238,7 +238,6 @@ static void F(System.Collections.Generic.IEnumerable<(int a, int b)> ie)
-
@@ -247,17 +246,17 @@ static void F(System.Collections.Generic.IEnumerable<(int a, int b)> ie)
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Retargeting/NoPia.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Retargeting/NoPia.cs
index f8cff8a57d3fb..2324226de2c55 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/Retargeting/NoPia.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Retargeting/NoPia.cs
@@ -1499,7 +1499,7 @@ public object TestTupleLiteral()
public void TestDeconstruction()
{
int x, y;
- (x, y) = new C();
+ var resultingTuple = ((x, y) = new C());
}
public void Deconstruct(out int a, out int b) { a = b = 1; }
}";
@@ -1515,9 +1515,9 @@ public void TestDeconstruction()
// (14,16): error CS1768: Type 'ValueTuple' cannot be embedded because it has a generic argument. Consider setting the 'Embed Interop Types' property to false.
// return (1, 2);
Diagnostic(ErrorCode.ERR_GenericsUsedInNoPIAType, "(1, 2)").WithArguments("System.ValueTuple").WithLocation(14, 16),
- // (19,9): error CS1768: Type 'ValueTuple' cannot be embedded because it has a generic argument. Consider setting the 'Embed Interop Types' property to false.
- // (x, y) = new C();
- Diagnostic(ErrorCode.ERR_GenericsUsedInNoPIAType, "(x, y)").WithArguments("System.ValueTuple").WithLocation(19, 9)
+ // (19,31): error CS1768: Type 'ValueTuple' cannot be embedded because it has a generic argument. Consider setting the 'Embed Interop Types' property to false.
+ // var resultingTuple = ((x, y) = new C());
+ Diagnostic(ErrorCode.ERR_GenericsUsedInNoPIAType, "(x, y)").WithArguments("System.ValueTuple").WithLocation(19, 31)
};
var comp1 = CreateStandardCompilation(source, options: TestOptions.ReleaseDll,
diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs
index d824ee80622ef..27642e4b9bc6a 100644
--- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs
+++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/CompilationContext.cs
@@ -582,10 +582,12 @@ private static BoundStatement BindExpression(Binder binder, ExpressionSyntax syn
// type names which are bound to a representation of the type
// (but not System.Type) that the user can expand to see the
// base type. Instead, we only allow valid C# expressions.
- var expression = binder.BindValue(syntax, diagnostics, Binder.BindValueKind.RValue);
+ var expression = IsDeconstruction(syntax)
+ ? binder.BindDeconstruction((AssignmentExpressionSyntax)syntax, diagnostics, resultIsUsedOverride: true)
+ : binder.BindValue(syntax, diagnostics, Binder.BindValueKind.RValue);
if (diagnostics.HasAnyErrors())
{
- resultProperties = default(ResultProperties);
+ resultProperties = default;
return null;
}
@@ -639,6 +641,17 @@ private static BoundStatement BindExpression(Binder binder, ExpressionSyntax syn
return new BoundReturnStatement(syntax, RefKind.None, expression) { WasCompilerGenerated = true };
}
+ private static bool IsDeconstruction(ExpressionSyntax syntax)
+ {
+ if (syntax.Kind() != SyntaxKind.SimpleAssignmentExpression)
+ {
+ return false;
+ }
+
+ var node = (AssignmentExpressionSyntax)syntax;
+ return node.Left.Kind() == SyntaxKind.TupleExpression || node.Left.Kind() == SyntaxKind.DeclarationExpression;
+ }
+
private static BoundStatement BindStatement(Binder binder, StatementSyntax syntax, DiagnosticBag diagnostics, out ResultProperties properties)
{
properties = new ResultProperties(DkmClrCompilationResultFlags.PotentialSideEffect | DkmClrCompilationResultFlags.ReadOnlyResult);