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 0b16f51bb9727..d6ff6c994cb3a 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 4c997b0a50da3..8fbf4ee14eca4 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 33018de9ed8e6..cab3ac8fb3df1 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 c56f348508b82..3d525448d95e6 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