From 86e29714af8fb7b1ea3b04a01309e0eba52b2f6c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 16 Jun 2017 18:04:59 -0700 Subject: [PATCH 01/10] Don't require ValueTuple type in deconstruction if return value is not used --- .../Portable/Binder/Binder_Deconstruct.cs | 40 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 6 + .../CSharp/Portable/BoundTree/Expression.cs | 15 + .../CSharp/Portable/CodeGen/EmitExpression.cs | 4 + .../FlowAnalysis/PreciseAbstractFlowPass.cs | 5 + .../Generated/BoundNodes.xml.Generated.cs | 78 ++- ...writer_DeconstructionAssignmentOperator.cs | 19 +- .../Symbols/Tuples/TupleTypeSymbol.cs | 2 +- .../Emit/CodeGen/CodeGenDeconstructTests.cs | 490 +++++++++++------- .../EditAndContinue/EditAndContinueTests.cs | 153 +++--- .../EditAndContinue/LocalSlotMappingTests.cs | 52 +- .../CSharp/Test/Emit/PDB/PDBTests.cs | 74 ++- .../CSharp/Test/Emit/PDB/PDBTupleTests.cs | 21 +- .../Test/Symbol/Symbols/Retargeting/NoPia.cs | 8 +- 14 files changed, 606 insertions(+), 361 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 366426e86abbe..2badec19c1c7b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -108,6 +108,8 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag ArrayBuilder checkedVariables, DiagnosticBag diagnostics) { + bool resultIsUsed = IsDeconstructionResultUsed(left); + if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType()) { // we could still not infer a type for the RHS @@ -117,9 +119,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, resultIsUsed ? diagnostics : null, hasErrors: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, constantValueOpt: null, type: type, hasErrors: true), + resultIsUsed, voidType, hasErrors: true); } @@ -135,7 +138,7 @@ private BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Diag FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, hasErrors); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, resultIsUsed ? diagnostics : null, hasErrors); TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type; var boundConversion = new BoundConversion( @@ -149,7 +152,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 bool IsDeconstructionResultUsed(ExpressionSyntax left) + { + var parent = left.Parent; + if (parent is null || left.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 doesn'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. diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index fca8f89687e1d..43c3295adc799 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -396,6 +396,12 @@ + + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index fc3f831bd67a3..2c33c8eac8aa0 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -837,6 +837,21 @@ public override void Accept(OperationVisitor visitor) } } + internal sealed partial class BoundVoid : BoundExpression + { + protected override OperationKind ExpressionKind => OperationKind.None; + + public override void Accept(OperationVisitor visitor) + { + visitor.VisitNoneOperation(this); + } + + public override TResult Accept(OperationVisitor visitor, TArgument argument) + { + return visitor.VisitNoneOperation(this, argument); + } + } + internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpression { // TODO: implement IOperation for pattern-matching constructs (https://github.com/dotnet/roslyn/issues/8699) diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index 741a08cf25fea..b94c0fcf13b1f 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -306,6 +306,10 @@ private void EmitExpressionCore(BoundExpression expression, bool used) EmitThrowExpression((BoundThrowExpression)expression, used); break; + case BoundKind.Void: + Debug.Assert(!used); + break; + default: // Code gen should not be invoked if there are errors. Debug.Assert(expression.Kind != BoundKind.BadExpression); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 5fa5a1a60625b..742e9d20e5eef 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -923,6 +923,11 @@ public override BoundNode VisitAttribute(BoundAttribute node) return null; } + public override BoundNode VisitVoid(BoundVoid node) + { + return null; + } + public override BoundNode VisitThrowExpression(BoundThrowExpression node) { VisitRvalue(node.Expression); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index d624e3ee0bab3..561f1d63869b1 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -45,6 +45,7 @@ internal enum BoundKind: byte CompoundAssignmentOperator, AssignmentOperator, DeconstructionAssignmentOperator, + Void, NullCoalescingOperator, ConditionalOperator, ArrayAccess, @@ -1160,7 +1161,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 +1171,7 @@ public BoundDeconstructionAssignmentOperator(SyntaxNode syntax, BoundTupleExpres this.Left = left; this.Right = right; + this.IsUsed = isUsed; } @@ -1177,16 +1179,54 @@ 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 || isUsed != this.IsUsed || type != this.Type) + { + var result = new BoundDeconstructionAssignmentOperator(this.Syntax, left, right, isUsed, type, this.HasErrors); + result.WasCompilerGenerated = this.WasCompilerGenerated; + return result; + } + return this; + } + } + + internal sealed partial class BoundVoid : BoundExpression + { + public BoundVoid(SyntaxNode syntax, TypeSymbol type, bool hasErrors) + : base(BoundKind.Void, syntax, type, hasErrors) + { + + Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + } + + public BoundVoid(SyntaxNode syntax, TypeSymbol type) + : base(BoundKind.Void, syntax, type) + { + + Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + } + + + public override BoundNode Accept(BoundTreeVisitor visitor) + { + return visitor.VisitVoid(this); + } + + public BoundVoid Update(TypeSymbol type) { - if (left != this.Left || right != this.Right || type != this.Type) + if (type != this.Type) { - var result = new BoundDeconstructionAssignmentOperator(this.Syntax, left, right, type, this.HasErrors); + var result = new BoundVoid(this.Syntax, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -6137,6 +6177,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitAssignmentOperator(node as BoundAssignmentOperator, arg); case BoundKind.DeconstructionAssignmentOperator: return VisitDeconstructionAssignmentOperator(node as BoundDeconstructionAssignmentOperator, arg); + case BoundKind.Void: + return VisitVoid(node as BoundVoid, arg); case BoundKind.NullCoalescingOperator: return VisitNullCoalescingOperator(node as BoundNullCoalescingOperator, arg); case BoundKind.ConditionalOperator: @@ -6493,6 +6535,10 @@ public virtual R VisitDeconstructionAssignmentOperator(BoundDeconstructionAssign { return this.DefaultVisit(node, arg); } + public virtual R VisitVoid(BoundVoid node, A arg) + { + return this.DefaultVisit(node, arg); + } public virtual R VisitNullCoalescingOperator(BoundNullCoalescingOperator node, A arg) { return this.DefaultVisit(node, arg); @@ -7093,6 +7139,10 @@ public virtual BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructi { return this.DefaultVisit(node); } + public virtual BoundNode VisitVoid(BoundVoid node) + { + return this.DefaultVisit(node); + } public virtual BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { return this.DefaultVisit(node); @@ -7720,6 +7770,10 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct this.Visit(node.Right); return null; } + public override BoundNode VisitVoid(BoundVoid node) + { + return null; + } public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { this.Visit(node.LeftOperand); @@ -8509,7 +8563,12 @@ 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 VisitVoid(BoundVoid node) + { + TypeSymbol type = this.VisitType(node.Type); + return node.Update(type); } public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { @@ -9503,6 +9562,15 @@ 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) + } + ); + } + public override TreeDumperNode VisitVoid(BoundVoid node, object arg) + { + return new TreeDumperNode("void", null, new TreeDumperNode[] + { new TreeDumperNode("type", node.Type, null) } ); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index a97ae93473c70..0f0a1b71bb1d8 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,18 +32,25 @@ 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, inInit: true); + if (!returnValue.HasErrors) { - returnTuple = VisitExpression(returnTuple); + if (isUsed) + { + returnValue = VisitExpression(returnValue); + } + else + { + returnValue = new BoundVoid(left.Syntax, left.Type); + } } - BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnTuple); + BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); return result; 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..c7e82438acf05 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,119 @@ public void Deconstruct(out int a, out int b) comp.VerifyDiagnostics(); } + [Fact] + public void ValueTupleNotRequiredIfReturnIsUsed() + { + 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] + public void ValueTupleNotRequiredIfReturnIsUsed2() + { + 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] + public void ValueTupleRequiredRightHandSideIsTuple() + { + 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 +1305,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 +1336,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 +1353,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 +1381,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 +1390,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 +1787,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 +3464,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 +3474,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 +3545,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 +3642,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 +3665,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 +4000,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 +5729,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 +5741,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 }"); 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/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 2c33c8eac8aa0..2422a4b2aefbf 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -837,7 +837,7 @@ public override void Accept(OperationVisitor visitor) } } - internal sealed partial class BoundVoid : BoundExpression + internal sealed partial class BoundUnusedResult : BoundExpression { protected override OperationKind ExpressionKind => OperationKind.None; diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index b94c0fcf13b1f..0efed4a615d82 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -306,7 +306,7 @@ private void EmitExpressionCore(BoundExpression expression, bool used) EmitThrowExpression((BoundThrowExpression)expression, used); break; - case BoundKind.Void: + case BoundKind.UnusedResult: Debug.Assert(!used); break; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 742e9d20e5eef..cc6515d377c0f 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -923,7 +923,7 @@ public override BoundNode VisitAttribute(BoundAttribute node) return null; } - public override BoundNode VisitVoid(BoundVoid node) + public override BoundNode VisitUnusedResult(BoundUnusedResult node) { return null; } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 561f1d63869b1..6a17a76325bc1 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -45,7 +45,7 @@ internal enum BoundKind: byte CompoundAssignmentOperator, AssignmentOperator, DeconstructionAssignmentOperator, - Void, + UnusedResult, NullCoalescingOperator, ConditionalOperator, ArrayAccess, @@ -1198,18 +1198,18 @@ public BoundDeconstructionAssignmentOperator Update(BoundTupleExpression left, B } } - internal sealed partial class BoundVoid : BoundExpression + internal sealed partial class BoundUnusedResult : BoundExpression { - public BoundVoid(SyntaxNode syntax, TypeSymbol type, bool hasErrors) - : base(BoundKind.Void, syntax, type, hasErrors) + public BoundUnusedResult(SyntaxNode syntax, TypeSymbol type, bool hasErrors) + : base(BoundKind.UnusedResult, syntax, type, hasErrors) { Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); } - public BoundVoid(SyntaxNode syntax, TypeSymbol type) - : base(BoundKind.Void, syntax, type) + public BoundUnusedResult(SyntaxNode syntax, TypeSymbol type) + : base(BoundKind.UnusedResult, syntax, type) { Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); @@ -1219,14 +1219,14 @@ public BoundVoid(SyntaxNode syntax, TypeSymbol type) public override BoundNode Accept(BoundTreeVisitor visitor) { - return visitor.VisitVoid(this); + return visitor.VisitUnusedResult(this); } - public BoundVoid Update(TypeSymbol type) + public BoundUnusedResult Update(TypeSymbol type) { if (type != this.Type) { - var result = new BoundVoid(this.Syntax, type, this.HasErrors); + var result = new BoundUnusedResult(this.Syntax, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -6177,8 +6177,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitAssignmentOperator(node as BoundAssignmentOperator, arg); case BoundKind.DeconstructionAssignmentOperator: return VisitDeconstructionAssignmentOperator(node as BoundDeconstructionAssignmentOperator, arg); - case BoundKind.Void: - return VisitVoid(node as BoundVoid, arg); + case BoundKind.UnusedResult: + return VisitUnusedResult(node as BoundUnusedResult, arg); case BoundKind.NullCoalescingOperator: return VisitNullCoalescingOperator(node as BoundNullCoalescingOperator, arg); case BoundKind.ConditionalOperator: @@ -6535,7 +6535,7 @@ public virtual R VisitDeconstructionAssignmentOperator(BoundDeconstructionAssign { return this.DefaultVisit(node, arg); } - public virtual R VisitVoid(BoundVoid node, A arg) + public virtual R VisitUnusedResult(BoundUnusedResult node, A arg) { return this.DefaultVisit(node, arg); } @@ -7139,7 +7139,7 @@ public virtual BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructi { return this.DefaultVisit(node); } - public virtual BoundNode VisitVoid(BoundVoid node) + public virtual BoundNode VisitUnusedResult(BoundUnusedResult node) { return this.DefaultVisit(node); } @@ -7770,7 +7770,7 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct this.Visit(node.Right); return null; } - public override BoundNode VisitVoid(BoundVoid node) + public override BoundNode VisitUnusedResult(BoundUnusedResult node) { return null; } @@ -8565,7 +8565,7 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct TypeSymbol type = this.VisitType(node.Type); return node.Update(left, right, node.IsUsed, type); } - public override BoundNode VisitVoid(BoundVoid node) + public override BoundNode VisitUnusedResult(BoundUnusedResult node) { TypeSymbol type = this.VisitType(node.Type); return node.Update(type); @@ -9567,9 +9567,9 @@ public override TreeDumperNode VisitDeconstructionAssignmentOperator(BoundDecons } ); } - public override TreeDumperNode VisitVoid(BoundVoid node, object arg) + public override TreeDumperNode VisitUnusedResult(BoundUnusedResult node, object arg) { - return new TreeDumperNode("void", null, new TreeDumperNode[] + return new TreeDumperNode("unusedResult", null, new TreeDumperNode[] { new TreeDumperNode("type", node.Type, null) } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index 0f0a1b71bb1d8..e53c8282302a2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -47,7 +47,7 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers } else { - returnValue = new BoundVoid(left.Syntax, left.Type); + returnValue = new BoundUnusedResult(left.Syntax, left.Type); } } BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index 11375610f25a0..b6c68b856131d 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -1090,6 +1090,7 @@ public void Deconstruct(out int a, out int b) } [Fact] + [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")] public void ValueTupleNotRequiredIfReturnIsNotUsed() { string source = @" @@ -1138,7 +1139,8 @@ public void Deconstruct(out int a, out int b) } [Fact] - public void ValueTupleNotRequiredIfReturnIsUsed2() + [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")] + public void ValueTupleNotRequiredIfReturnIsNotUsed2() { string source = @" class C @@ -1182,7 +1184,8 @@ public void Deconstruct(out int a, out int b) } [Fact] - public void ValueTupleRequiredRightHandSideIsTuple() + [WorkItem(18629, "https://github.com/dotnet/roslyn/issues/18629")] + public void ValueTupleRequiredWhenRightHandSideIsTuple() { string source = @" class C @@ -7386,6 +7389,64 @@ Error M() ); } + [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() { From 5ca6259d157e8f9c0f7dbe8cdc1bd3d958eb9a07 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 21 Jun 2017 20:13:06 -0700 Subject: [PATCH 05/10] Address PR feedback from Aleksey --- .../Portable/Binder/Binder_Deconstruct.cs | 17 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 5 - .../CSharp/Portable/BoundTree/Expression.cs | 15 -- .../CSharp/Portable/CodeGen/EmitExpression.cs | 4 - .../FlowAnalysis/PreciseAbstractFlowPass.cs | 5 - .../Generated/BoundNodes.xml.Generated.cs | 64 ------ .../Lowering/LocalRewriter/LocalRewriter.cs | 3 +- ...writer_DeconstructionAssignmentOperator.cs | 34 +-- .../Emit/CodeGen/CodeGenDeconstructTests.cs | 216 +++++++++++------- .../EditAndContinue/EditAndContinueTests.cs | 40 ++-- .../CSharp/Test/Emit/PDB/PDBTupleTests.cs | 20 +- 11 files changed, 196 insertions(+), 227 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 685c91ba1465b..88b47b52f8f0c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -78,7 +78,7 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia /// 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 bind deconstructions (both assignments and declarations) as expression-statements + /// The expression evaluator needs to bind deconstructions (both assignments and declarations) as expression-statements /// and still access the returned value /// /// @@ -122,7 +122,7 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia var type = boundRHS.Type ?? voidType; return new BoundDeconstructionAssignmentOperator( node, - DeconstructionVariablesAsTuple(node, checkedVariables, resultIsUsed ? diagnostics : null, 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, @@ -141,7 +141,7 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, resultIsUsed ? diagnostics : null, hasErrors); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed); TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type; var boundConversion = new BoundConversion( @@ -504,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); @@ -515,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); @@ -541,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 7a800f748cb2e..ecff5f3222312 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -399,11 +399,6 @@ - - - - - diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 2422a4b2aefbf..fc3f831bd67a3 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -837,21 +837,6 @@ public override void Accept(OperationVisitor visitor) } } - internal sealed partial class BoundUnusedResult : BoundExpression - { - protected override OperationKind ExpressionKind => OperationKind.None; - - public override void Accept(OperationVisitor visitor) - { - visitor.VisitNoneOperation(this); - } - - public override TResult Accept(OperationVisitor visitor, TArgument argument) - { - return visitor.VisitNoneOperation(this, argument); - } - } - internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpression { // TODO: implement IOperation for pattern-matching constructs (https://github.com/dotnet/roslyn/issues/8699) diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index 0efed4a615d82..741a08cf25fea 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -306,10 +306,6 @@ private void EmitExpressionCore(BoundExpression expression, bool used) EmitThrowExpression((BoundThrowExpression)expression, used); break; - case BoundKind.UnusedResult: - Debug.Assert(!used); - break; - default: // Code gen should not be invoked if there are errors. Debug.Assert(expression.Kind != BoundKind.BadExpression); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index cc6515d377c0f..5fa5a1a60625b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -923,11 +923,6 @@ public override BoundNode VisitAttribute(BoundAttribute node) return null; } - public override BoundNode VisitUnusedResult(BoundUnusedResult node) - { - return null; - } - public override BoundNode VisitThrowExpression(BoundThrowExpression node) { VisitRvalue(node.Expression); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 6a17a76325bc1..d4e811604f9fe 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -45,7 +45,6 @@ internal enum BoundKind: byte CompoundAssignmentOperator, AssignmentOperator, DeconstructionAssignmentOperator, - UnusedResult, NullCoalescingOperator, ConditionalOperator, ArrayAccess, @@ -1198,42 +1197,6 @@ public BoundDeconstructionAssignmentOperator Update(BoundTupleExpression left, B } } - internal sealed partial class BoundUnusedResult : BoundExpression - { - public BoundUnusedResult(SyntaxNode syntax, TypeSymbol type, bool hasErrors) - : base(BoundKind.UnusedResult, syntax, type, hasErrors) - { - - Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); - - } - - public BoundUnusedResult(SyntaxNode syntax, TypeSymbol type) - : base(BoundKind.UnusedResult, syntax, type) - { - - Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); - - } - - - public override BoundNode Accept(BoundTreeVisitor visitor) - { - return visitor.VisitUnusedResult(this); - } - - public BoundUnusedResult Update(TypeSymbol type) - { - if (type != this.Type) - { - var result = new BoundUnusedResult(this.Syntax, type, this.HasErrors); - result.WasCompilerGenerated = this.WasCompilerGenerated; - return result; - } - return this; - } - } - internal sealed partial class BoundNullCoalescingOperator : BoundExpression { public BoundNullCoalescingOperator(SyntaxNode syntax, BoundExpression leftOperand, BoundExpression rightOperand, Conversion leftConversion, TypeSymbol type, bool hasErrors = false) @@ -6177,8 +6140,6 @@ internal R VisitInternal(BoundNode node, A arg) return VisitAssignmentOperator(node as BoundAssignmentOperator, arg); case BoundKind.DeconstructionAssignmentOperator: return VisitDeconstructionAssignmentOperator(node as BoundDeconstructionAssignmentOperator, arg); - case BoundKind.UnusedResult: - return VisitUnusedResult(node as BoundUnusedResult, arg); case BoundKind.NullCoalescingOperator: return VisitNullCoalescingOperator(node as BoundNullCoalescingOperator, arg); case BoundKind.ConditionalOperator: @@ -6535,10 +6496,6 @@ public virtual R VisitDeconstructionAssignmentOperator(BoundDeconstructionAssign { return this.DefaultVisit(node, arg); } - public virtual R VisitUnusedResult(BoundUnusedResult node, A arg) - { - return this.DefaultVisit(node, arg); - } public virtual R VisitNullCoalescingOperator(BoundNullCoalescingOperator node, A arg) { return this.DefaultVisit(node, arg); @@ -7139,10 +7096,6 @@ public virtual BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructi { return this.DefaultVisit(node); } - public virtual BoundNode VisitUnusedResult(BoundUnusedResult node) - { - return this.DefaultVisit(node); - } public virtual BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { return this.DefaultVisit(node); @@ -7770,10 +7723,6 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct this.Visit(node.Right); return null; } - public override BoundNode VisitUnusedResult(BoundUnusedResult node) - { - return null; - } public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { this.Visit(node.LeftOperand); @@ -8565,11 +8514,6 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct TypeSymbol type = this.VisitType(node.Type); return node.Update(left, right, node.IsUsed, type); } - public override BoundNode VisitUnusedResult(BoundUnusedResult node) - { - TypeSymbol type = this.VisitType(node.Type); - return node.Update(type); - } public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { BoundExpression leftOperand = (BoundExpression)this.Visit(node.LeftOperand); @@ -9567,14 +9511,6 @@ public override TreeDumperNode VisitDeconstructionAssignmentOperator(BoundDecons } ); } - public override TreeDumperNode VisitUnusedResult(BoundUnusedResult node, object arg) - { - return new TreeDumperNode("unusedResult", null, new TreeDumperNode[] - { - new TreeDumperNode("type", node.Type, null) - } - ); - } public override TreeDumperNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node, object arg) { return new TreeDumperNode("nullCoalescingOperator", null, new TreeDumperNode[] diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index c3c9afd43013d..413ef8f3080e0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -181,7 +181,8 @@ private BoundExpression VisitExpressionImpl(BoundExpression node) // 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. 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) || + node.Kind == BoundKind.DeconstructionAssignmentOperator); return visited; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index e53c8282302a2..a58a6d0f9add3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -38,17 +38,10 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers var effects = DeconstructionSideEffects.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, temps, effects.init); - BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, inInit: true); + BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); if (!returnValue.HasErrors) { - if (isUsed) - { - returnValue = VisitExpression(returnValue); - } - else - { - returnValue = new BoundUnusedResult(left.Syntax, left.Type); - } + returnValue = VisitExpression(returnValue); } BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); @@ -64,9 +57,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); @@ -82,7 +75,7 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers if (leftTargets[i].HasNestedVariables) { resultPart = ApplyDeconstructionConversion(leftTargets[i].NestedVariables, rightParts[i], - underlyingConversions[i], temps, effects, inInit); + underlyingConversions[i], temps, effects, isUsed, inInit); } else { @@ -105,11 +98,20 @@ private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Convers 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 + { + // When a deconstruction is not used, we use the last value of the LHS as the return value for the lowered form + return builder.ToImmutableAndFree().Last(); + } } private ImmutableArray GetRightParts(BoundExpression right, Conversion conversion, diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index b6c68b856131d..31382e769f845 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -1183,6 +1183,56 @@ public void Deconstruct(out int a, out int b) 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() @@ -3464,7 +3514,7 @@ static void Main() comp.VerifyIL("C.Main", @"{ - // Code size 70 (0x46) + // Code size 72 (0x48) .maxstack 2 .locals init (System.Collections.Generic.IEnumerator<(int, int)> V_0, int V_1, //x1 @@ -3474,33 +3524,35 @@ .maxstack 2 IL_000a: stloc.0 .try { - IL_000b: br.s IL_0031 + IL_000b: br.s IL_0033 IL_000d: ldloc.0 IL_000e: callvirt ""(int, int) System.Collections.Generic.IEnumerator<(int, int)>.Current.get"" IL_0013: dup IL_0014: ldfld ""int System.ValueTuple.Item1"" IL_0019: stloc.1 - IL_001a: ldfld ""int System.ValueTuple.Item2"" - IL_001f: stloc.2 - IL_0020: ldloc.1 - IL_0021: box ""int"" - IL_0026: ldloc.2 - IL_0027: box ""int"" - IL_002c: call ""void C.Print(object, object)"" - IL_0031: ldloc.0 - IL_0032: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" - IL_0037: brtrue.s IL_000d - IL_0039: leave.s IL_0045 + IL_001a: dup + IL_001b: ldfld ""int System.ValueTuple.Item2"" + IL_0020: stloc.2 + IL_0021: pop + IL_0022: ldloc.1 + IL_0023: box ""int"" + IL_0028: ldloc.2 + IL_0029: box ""int"" + IL_002e: call ""void C.Print(object, object)"" + IL_0033: ldloc.0 + IL_0034: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_0039: brtrue.s IL_000d + IL_003b: leave.s IL_0047 } finally { - IL_003b: ldloc.0 - IL_003c: brfalse.s IL_0044 - IL_003e: ldloc.0 - IL_003f: callvirt ""void System.IDisposable.Dispose()"" - IL_0044: endfinally + IL_003d: ldloc.0 + IL_003e: brfalse.s IL_0046 + IL_0040: ldloc.0 + IL_0041: callvirt ""void System.IDisposable.Dispose()"" + IL_0046: endfinally } - IL_0045: ret + IL_0047: ret }"); } @@ -3548,7 +3600,7 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @"{ - // Code size 91 (0x5b) + // Code size 93 (0x5d) .maxstack 4 .locals init ((int, int)[] V_0, int V_1, @@ -3558,47 +3610,49 @@ .maxstack 4 IL_0005: stloc.0 IL_0006: ldc.i4.0 IL_0007: stloc.1 - IL_0008: br.s IL_0054 + IL_0008: br.s IL_0056 IL_000a: ldloc.0 IL_000b: ldloc.1 IL_000c: ldelem ""System.ValueTuple"" IL_0011: dup IL_0012: ldfld ""int System.ValueTuple.Item1"" IL_0017: stloc.2 - IL_0018: ldfld ""int System.ValueTuple.Item2"" - IL_001d: stloc.3 - IL_001e: ldc.i4.4 - IL_001f: newarr ""object"" - IL_0024: dup - IL_0025: ldc.i4.0 - IL_0026: ldloc.2 - IL_0027: box ""int"" - IL_002c: stelem.ref - IL_002d: dup - IL_002e: ldc.i4.1 - IL_002f: ldstr "" "" - IL_0034: stelem.ref - IL_0035: dup - IL_0036: ldc.i4.2 - IL_0037: ldloc.3 - IL_0038: box ""int"" - IL_003d: stelem.ref - IL_003e: dup - IL_003f: ldc.i4.3 - IL_0040: ldstr "" - "" - IL_0045: stelem.ref - IL_0046: call ""string string.Concat(params object[])"" - IL_004b: call ""void System.Console.Write(string)"" - IL_0050: ldloc.1 - IL_0051: ldc.i4.1 - IL_0052: add - IL_0053: stloc.1 - IL_0054: ldloc.1 - IL_0055: ldloc.0 - IL_0056: ldlen - IL_0057: conv.i4 - IL_0058: blt.s IL_000a - IL_005a: ret + IL_0018: dup + IL_0019: ldfld ""int System.ValueTuple.Item2"" + IL_001e: stloc.3 + IL_001f: pop + IL_0020: ldc.i4.4 + IL_0021: newarr ""object"" + IL_0026: dup + IL_0027: ldc.i4.0 + IL_0028: ldloc.2 + IL_0029: box ""int"" + IL_002e: stelem.ref + IL_002f: dup + IL_0030: ldc.i4.1 + IL_0031: ldstr "" "" + IL_0036: stelem.ref + IL_0037: dup + IL_0038: ldc.i4.2 + IL_0039: ldloc.3 + IL_003a: box ""int"" + IL_003f: stelem.ref + IL_0040: dup + IL_0041: ldc.i4.3 + IL_0042: ldstr "" - "" + IL_0047: stelem.ref + IL_0048: call ""string string.Concat(params object[])"" + IL_004d: call ""void System.Console.Write(string)"" + IL_0052: ldloc.1 + IL_0053: ldc.i4.1 + IL_0054: add + IL_0055: stloc.1 + IL_0056: ldloc.1 + IL_0057: ldloc.0 + IL_0058: ldlen + IL_0059: conv.i4 + IL_005a: blt.s IL_000a + IL_005c: ret }"); } @@ -3645,7 +3699,7 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @"{ - // Code size 106 (0x6a) + // Code size 108 (0x6c) .maxstack 3 .locals init ((int, int)[,] V_0, int V_1, @@ -3668,12 +3722,12 @@ .maxstack 3 IL_0017: ldc.i4.0 IL_0018: callvirt ""int System.Array.GetLowerBound(int)"" IL_001d: stloc.3 - IL_001e: br.s IL_0065 + IL_001e: br.s IL_0067 IL_0020: ldloc.0 IL_0021: ldc.i4.1 IL_0022: callvirt ""int System.Array.GetLowerBound(int)"" IL_0027: stloc.s V_4 - IL_0029: br.s IL_005c + IL_0029: br.s IL_005e IL_002b: ldloc.0 IL_002c: ldloc.3 IL_002d: ldloc.s V_4 @@ -3681,28 +3735,30 @@ .maxstack 3 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 + IL_003c: dup + IL_003d: ldfld ""int System.ValueTuple.Item2"" + IL_0042: stloc.s V_6 + IL_0044: pop + IL_0045: ldloc.s V_5 + IL_0047: box ""int"" + IL_004c: ldloc.s V_6 + IL_004e: box ""int"" + IL_0053: call ""void C.Print(object, object)"" + IL_0058: ldloc.s V_4 + IL_005a: ldc.i4.1 + IL_005b: add + IL_005c: stloc.s V_4 + IL_005e: ldloc.s V_4 + IL_0060: ldloc.2 + IL_0061: ble.s IL_002b + IL_0063: ldloc.3 + IL_0064: ldc.i4.1 + IL_0065: add + IL_0066: stloc.3 + IL_0067: ldloc.3 + IL_0068: ldloc.1 + IL_0069: ble.s IL_0020 + IL_006b: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index a5cf7864c599b..0f6a5d3aa61b4 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -8311,7 +8311,7 @@ .maxstack 2 diff2.VerifyIL("C.G", @" { - // Code size 61 (0x3d) + // Code size 63 (0x3f) .maxstack 2 .locals init ([unchanged] V_0, [int] V_1, @@ -8331,30 +8331,32 @@ .maxstack 2 IL_0007: stloc.s V_9 IL_0009: ldc.i4.0 IL_000a: stloc.s V_10 - IL_000c: br.s IL_0034 + IL_000c: br.s IL_0036 IL_000e: ldloc.s V_9 IL_0010: ldloc.s V_10 IL_0012: ldelem ""System.ValueTuple"" 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 + IL_001e: dup + IL_001f: ldfld ""(bool, double) System.ValueTuple.Item2"" + IL_0024: stloc.s V_11 + IL_0026: pop + IL_0027: nop + IL_0028: ldloc.2 + IL_0029: call ""void System.Console.WriteLine(int)"" + IL_002e: nop + IL_002f: nop + IL_0030: ldloc.s V_10 + IL_0032: ldc.i4.1 + IL_0033: add + IL_0034: stloc.s V_10 + IL_0036: ldloc.s V_10 + IL_0038: ldloc.s V_9 + IL_003a: ldlen + IL_003b: conv.i4 + IL_003c: blt.s IL_000e + IL_003e: ret } "); } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs index 219f1803a6fbf..772f776ff5384 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTupleTests.cs @@ -246,17 +246,17 @@ static void F(System.Collections.Generic.IEnumerable<(int a, int b)> ie)