From 42f58dd04b9ab0ff7bbfdb4b1aedbfe32be2c01a Mon Sep 17 00:00:00 2001 From: Neal Gafter Date: Mon, 5 Jun 2017 16:16:36 -0700 Subject: [PATCH] Fix code gen for pattern switch with constant expression (#19737) * Fix code gen for pattern switch with constant expression Fixes #19731 * Add instrumentation for pattern switch with constant expression Fixes #17090 * Incorrect code for generic pattern switch on a constant expression Fixes #19734 --- .../Portable/BoundTree/DecisionTreeBuilder.cs | 13 +- .../LocalRewriter_PatternSwitchStatement.cs | 144 +++--- .../CSharp/Test/Emit/CodeGen/SwitchTests.cs | 29 +- .../EditAndContinue/LocalSlotMappingTests.cs | 284 ++++++------ .../CSharp/Test/Emit/PDB/PDBTests.cs | 430 ++++++++++++++++++ 5 files changed, 684 insertions(+), 216 deletions(-) diff --git a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs index ecb6e21df3a..1d480568cc3 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs @@ -308,8 +308,17 @@ private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern if (forType == null) { var type = value.Value.Type; - var narrowedExpression = GetBoundPatternMatchingLocal(type); - forType = new DecisionTree.ByValue(narrowedExpression, type.TupleUnderlyingTypeOrSelf(), narrowedExpression.LocalSymbol); + if (byType.Type.Equals(type, TypeCompareKind.AllIgnoreOptions)) + { + // reuse the input expression when we have an equivalent type to reduce the number of generated temps + forType = new DecisionTree.ByValue(byType.Expression, type.TupleUnderlyingTypeOrSelf(), null); + } + else + { + var narrowedExpression = GetBoundPatternMatchingLocal(type); + forType = new DecisionTree.ByValue(narrowedExpression, type.TupleUnderlyingTypeOrSelf(), narrowedExpression.LocalSymbol); + } + byType.TypeAndDecision.Add(new KeyValuePair(type, forType)); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs index c0c724b589c..cc6b37722cf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs @@ -60,13 +60,20 @@ private BoundStatement MakeLoweredForm(BoundPatternSwitchStatement node) var expression = _localRewriter.VisitExpression(node.Expression); var result = ArrayBuilder.GetInstance(); - if (expression.ConstantValue == null) + // EnC: We need to insert a hidden sequence point to handle function remapping in case + // the containing method is edited while methods invoked in the expression are being executed. + if (!node.WasCompilerGenerated && _localRewriter.Instrument) { - // EnC: We need to insert a hidden sequence point to handle function remapping in case - // the containing method is edited while methods invoked in the expression are being executed. - if (!node.WasCompilerGenerated && _localRewriter.Instrument) + var instrumentedExpression = _localRewriter._instrumenter.InstrumentSwitchStatementExpression(node, expression, _factory); + if (expression.ConstantValue == null) { - expression = _localRewriter._instrumenter.InstrumentSwitchStatementExpression(node, expression, _factory); + expression = instrumentedExpression; + } + else + { + // If the expression is a constant, we leave it alone (the decision tree lowering code needs + // to see that constant). But we add an additional leading statement with the instrumented expression. + result.Add(_factory.ExpressionStatement(instrumentedExpression)); } } @@ -172,7 +179,13 @@ private void LowerPatternSwitch(BoundExpression loweredExpression, BoundPatternS if (defaultLabel != null && !loweredDecisionTree.MatchIsComplete) { - Add(loweredDecisionTree, (e, t) => new DecisionTree.Guarded(loweredExpression, loweredExpression.Type, default(ImmutableArray>), defaultSection, null, defaultLabel)); + Add(loweredDecisionTree, (e, t) => new DecisionTree.Guarded( + expression: loweredExpression, + type: loweredExpression.Type, + bindings: default, + sectionSyntax: defaultSection, + guard: null, + label: defaultLabel)); } // We discard use-site diagnostics, as they have been reported during initial binding. @@ -248,61 +261,71 @@ private void LowerDecisionTree(BoundExpression expression, DecisionTree decision private void LowerDecisionTree(DecisionTree.ByType byType) { var inputConstant = byType.Expression.ConstantValue; + + // three-valued: true if input known null, false if input known non-null, null if not known. + bool? inputIsNull = null; if (inputConstant != null) { - if (inputConstant.IsNull) - { - // input is the constant null - LowerDecisionTree(byType.Expression, byType.WhenNull); - if (byType.WhenNull?.MatchIsComplete != true) - { - LowerDecisionTree(byType.Expression, byType.Default); - } - } - else + inputIsNull = inputConstant.IsNull; + } + + var defaultLabel = _factory.GenerateLabel("byTypeDefault"); + + if (byType.Type.CanContainNull()) + { + switch (inputIsNull) { - // input is a non-null constant - foreach (var kvp in byType.TypeAndDecision) - { - LowerDecisionTree(byType.Expression, kvp.Value); - if (kvp.Value.MatchIsComplete) + case true: { - return; + // Input is known to be null. Generate code for the null case only. + LowerDecisionTree(byType.Expression, byType.WhenNull); + if (byType.WhenNull?.MatchIsComplete != true) + { + _loweredDecisionTree.Add(_factory.Goto(defaultLabel)); + } + break; + } + case false: + { + // Input is known not to be null. Don't generate any code for the null case. + break; + } + case null: + { + // Unknown if the input is null. First test for null + var notNullLabel = _factory.GenerateLabel("notNull"); + var inputExpression = byType.Expression; + var objectType = _factory.SpecialType(SpecialType.System_Object); + var nullValue = _factory.Null(objectType); + BoundExpression notNull = + byType.Type.IsNullableType() + ? _localRewriter.RewriteNullableNullEquality( + _factory.Syntax, + BinaryOperatorKind.NullableNullNotEqual, + byType.Expression, + nullValue, + _factory.SpecialType(SpecialType.System_Boolean)) + : _factory.ObjectNotEqual(nullValue, _factory.Convert(objectType, byType.Expression)); + _loweredDecisionTree.Add(_factory.ConditionalGoto(notNull, notNullLabel, true)); + LowerDecisionTree(byType.Expression, byType.WhenNull); + if (byType.WhenNull?.MatchIsComplete != true) + { + _loweredDecisionTree.Add(_factory.Goto(defaultLabel)); + } + + _loweredDecisionTree.Add(_factory.Label(notNullLabel)); + break; } - } - - LowerDecisionTree(byType.Expression, byType.Default); } + } else { - var defaultLabel = _factory.GenerateLabel("byTypeDefault"); - - // input is not a constant - if (byType.Type.CanContainNull()) - { - // first test for null - var notNullLabel = _factory.GenerateLabel("notNull"); - var inputExpression = byType.Expression; - var objectType = _factory.SpecialType(SpecialType.System_Object); - var nullValue = _factory.Null(objectType); - BoundExpression notNull = byType.Type.IsNullableType() - ? _localRewriter.RewriteNullableNullEquality(_factory.Syntax, BinaryOperatorKind.NullableNullNotEqual, byType.Expression, nullValue, _factory.SpecialType(SpecialType.System_Boolean)) - : _factory.ObjectNotEqual(nullValue, _factory.Convert(objectType, byType.Expression)); - _loweredDecisionTree.Add(_factory.ConditionalGoto(notNull, notNullLabel, true)); - LowerDecisionTree(byType.Expression, byType.WhenNull); - if (byType.WhenNull?.MatchIsComplete != true) - { - _loweredDecisionTree.Add(_factory.Goto(defaultLabel)); - } - - _loweredDecisionTree.Add(_factory.Label(notNullLabel)); - } - else - { - Debug.Assert(byType.WhenNull == null); - } + Debug.Assert(byType.WhenNull == null); + } + if (inputIsNull != true) + { foreach (var td in byType.TypeAndDecision) { // then test for each type, sequentially @@ -314,11 +337,11 @@ private void LowerDecisionTree(DecisionTree.ByType byType) LowerDecisionTree(decision.Expression, decision); _loweredDecisionTree.Add(_factory.Label(failLabel)); } - - // finally, the default for when no type matches - _loweredDecisionTree.Add(_factory.Label(defaultLabel)); - LowerDecisionTree(byType.Expression, byType.Default); } + + // finally, the default for when no type matches + _loweredDecisionTree.Add(_factory.Label(defaultLabel)); + LowerDecisionTree(byType.Expression, byType.Default); } private BoundExpression TypeTestAndCopyToTemp(BoundExpression input, BoundExpression temp) @@ -363,8 +386,15 @@ private void LowerConstantValueDecision(DecisionTree.ByValue byValue) var value = byValue.Expression.ConstantValue.Value; Debug.Assert(value != null); - // because we are switching on a constant, the decision tree builder does not produce a (nonempty) ByValue - Debug.Assert(byValue.ValueAndDecision.Count == 0); + // If there is a matching value among the cases, that is the only one lowered. + if (byValue.ValueAndDecision.TryGetValue(value, out DecisionTree valueDecision)) + { + LowerDecisionTree(byValue.Expression, valueDecision); + if (valueDecision.MatchIsComplete) + { + return; + } + } LowerDecisionTree(byValue.Expression, byValue.Default); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 5a070ce61a7..aaebcfbd5dc 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -7653,23 +7653,26 @@ .locals init (object V_0) expectedOutput: "RemoveEmptyEntries"); compVerifier.VerifyIL("Program.Main", @"{ - // Code size 24 (0x18) + // Code size 26 (0x1a) .maxstack 1 .locals init (object V_0, - object V_1) //o + object V_1, //o + System.StringSplitOptions V_2) IL_0000: nop IL_0001: ldc.i4.1 - IL_0002: box ""System.StringSplitOptions"" - IL_0007: stloc.0 - IL_0008: br.s IL_000a - IL_000a: ldloc.0 - IL_000b: stloc.1 - IL_000c: br.s IL_000e - IL_000e: ldloc.1 - IL_000f: call ""void System.Console.WriteLine(object)"" - IL_0014: nop - IL_0015: br.s IL_0017 - IL_0017: ret + IL_0002: stloc.2 + IL_0003: ldc.i4.1 + IL_0004: box ""System.StringSplitOptions"" + IL_0009: stloc.0 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: stloc.1 + IL_000e: br.s IL_0010 + IL_0010: ldloc.1 + IL_0011: call ""void System.Console.WriteLine(object)"" + IL_0016: nop + IL_0017: br.s IL_0019 + IL_0019: ret }" ); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index 6331fc24368..525e292e8a6 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -2403,7 +2403,7 @@ static void M() v0.VerifyIL("C.M", @" { - // Code size 232 (0xe8) + // Code size 230 (0xe6) .maxstack 2 .locals init (object V_0, int V_1, @@ -2421,7 +2421,7 @@ .maxstack 2 IL_000a: stloc.0 IL_000b: ldloc.0 IL_000c: brtrue.s IL_0010 - IL_000e: br.s IL_0083 + IL_000e: br.s IL_0081 IL_0010: ldloc.0 IL_0011: stloc.s V_8 IL_0013: ldloc.s V_8 @@ -2440,7 +2440,7 @@ .maxstack 2 IL_002e: ldc.i4.1 IL_002f: beq.s IL_0033 IL_0031: br.s IL_0035 - IL_0033: br.s IL_0085 + IL_0033: br.s IL_0083 IL_0035: ldloc.0 IL_0036: stloc.s V_8 IL_0038: ldloc.s V_8 @@ -2454,76 +2454,74 @@ .maxstack 2 IL_0048: ldloc.s V_8 IL_004a: unbox.any ""byte"" IL_004f: stloc.2 - IL_0050: brfalse.s IL_005e - IL_0052: br.s IL_0092 + IL_0050: brfalse.s IL_005c + IL_0052: br.s IL_0090 IL_0054: ldloc.2 - IL_0055: stloc.2 - IL_0056: ldloc.2 - IL_0057: ldc.i4.1 - IL_0058: beq.s IL_005c - IL_005a: br.s IL_005e - IL_005c: br.s IL_00bc - IL_005e: ldloc.0 - IL_005f: stloc.s V_8 - IL_0061: ldloc.s V_8 - IL_0063: isinst ""int"" - IL_0068: ldnull - IL_0069: cgt.un - IL_006b: dup - IL_006c: brtrue.s IL_0071 - IL_006e: ldc.i4.0 - IL_006f: br.s IL_0078 - IL_0071: ldloc.s V_8 - IL_0073: unbox.any ""int"" - IL_0078: stloc.1 - IL_0079: brfalse.s IL_007f - IL_007b: br.s IL_00a6 - IL_007d: br.s IL_00c9 - IL_007f: ldloc.0 - IL_0080: stloc.0 - IL_0081: br.s IL_00d8 - IL_0083: br.s IL_00e7 - IL_0085: ldstr ""int 1"" - IL_008a: call ""void System.Console.WriteLine(string)"" - IL_008f: nop - IL_0090: br.s IL_00e7 - IL_0092: ldloc.2 - IL_0093: stloc.3 - IL_0094: call ""bool C.P()"" - IL_0099: brtrue.s IL_009d - IL_009b: br.s IL_0054 - IL_009d: ldloc.3 - IL_009e: call ""void System.Console.WriteLine(int)"" - IL_00a3: nop - IL_00a4: br.s IL_00e7 - IL_00a6: ldloc.1 - IL_00a7: stloc.s V_4 - IL_00a9: call ""bool C.P()"" - IL_00ae: brtrue.s IL_00b2 - IL_00b0: br.s IL_007d - IL_00b2: ldloc.s V_4 - IL_00b4: call ""void System.Console.WriteLine(int)"" - IL_00b9: nop - IL_00ba: br.s IL_00e7 - IL_00bc: ldstr ""byte 1"" - IL_00c1: call ""void System.Console.WriteLine(string)"" - IL_00c6: nop - IL_00c7: br.s IL_00e7 - IL_00c9: ldloc.1 - IL_00ca: stloc.s V_5 - IL_00cc: br.s IL_00ce - IL_00ce: ldloc.s V_5 - IL_00d0: call ""void System.Console.WriteLine(int)"" - IL_00d5: nop - IL_00d6: br.s IL_00e7 - IL_00d8: ldloc.0 - IL_00d9: stloc.s V_6 - IL_00db: br.s IL_00dd - IL_00dd: ldloc.s V_6 - IL_00df: call ""void System.Console.WriteLine(object)"" - IL_00e4: nop - IL_00e5: br.s IL_00e7 - IL_00e7: ret + IL_0055: ldc.i4.1 + IL_0056: beq.s IL_005a + IL_0058: br.s IL_005c + IL_005a: br.s IL_00ba + IL_005c: ldloc.0 + IL_005d: stloc.s V_8 + IL_005f: ldloc.s V_8 + IL_0061: isinst ""int"" + IL_0066: ldnull + IL_0067: cgt.un + IL_0069: dup + IL_006a: brtrue.s IL_006f + IL_006c: ldc.i4.0 + IL_006d: br.s IL_0076 + IL_006f: ldloc.s V_8 + IL_0071: unbox.any ""int"" + IL_0076: stloc.1 + IL_0077: brfalse.s IL_007d + IL_0079: br.s IL_00a4 + IL_007b: br.s IL_00c7 + IL_007d: ldloc.0 + IL_007e: stloc.0 + IL_007f: br.s IL_00d6 + IL_0081: br.s IL_00e5 + IL_0083: ldstr ""int 1"" + IL_0088: call ""void System.Console.WriteLine(string)"" + IL_008d: nop + IL_008e: br.s IL_00e5 + IL_0090: ldloc.2 + IL_0091: stloc.3 + IL_0092: call ""bool C.P()"" + IL_0097: brtrue.s IL_009b + IL_0099: br.s IL_0054 + IL_009b: ldloc.3 + IL_009c: call ""void System.Console.WriteLine(int)"" + IL_00a1: nop + IL_00a2: br.s IL_00e5 + IL_00a4: ldloc.1 + IL_00a5: stloc.s V_4 + IL_00a7: call ""bool C.P()"" + IL_00ac: brtrue.s IL_00b0 + IL_00ae: br.s IL_007b + IL_00b0: ldloc.s V_4 + IL_00b2: call ""void System.Console.WriteLine(int)"" + IL_00b7: nop + IL_00b8: br.s IL_00e5 + IL_00ba: ldstr ""byte 1"" + IL_00bf: call ""void System.Console.WriteLine(string)"" + IL_00c4: nop + IL_00c5: br.s IL_00e5 + IL_00c7: ldloc.1 + IL_00c8: stloc.s V_5 + IL_00ca: br.s IL_00cc + IL_00cc: ldloc.s V_5 + IL_00ce: call ""void System.Console.WriteLine(int)"" + IL_00d3: nop + IL_00d4: br.s IL_00e5 + IL_00d6: ldloc.0 + IL_00d7: stloc.s V_6 + IL_00d9: br.s IL_00db + IL_00db: ldloc.s V_6 + IL_00dd: call ""void System.Console.WriteLine(object)"" + IL_00e2: nop + IL_00e3: br.s IL_00e5 + IL_00e5: ret }"); var methodData0 = v0.TestData.GetMethodData("C.M"); var method0 = compilation0.GetMember("C.M"); @@ -2536,7 +2534,7 @@ .maxstack 2 diff1.VerifyIL("C.M", @" { - // Code size 232 (0xe8) + // Code size 230 (0xe6) .maxstack 2 .locals init (object V_0, int V_1, @@ -2555,7 +2553,7 @@ .maxstack 2 IL_000a: stloc.0 IL_000b: ldloc.0 IL_000c: brtrue.s IL_0010 - IL_000e: br.s IL_0083 + IL_000e: br.s IL_0081 IL_0010: ldloc.0 IL_0011: stloc.s V_9 IL_0013: ldloc.s V_9 @@ -2574,7 +2572,7 @@ .maxstack 2 IL_002e: ldc.i4.1 IL_002f: beq.s IL_0033 IL_0031: br.s IL_0035 - IL_0033: br.s IL_0085 + IL_0033: br.s IL_0083 IL_0035: ldloc.0 IL_0036: stloc.s V_9 IL_0038: ldloc.s V_9 @@ -2588,76 +2586,74 @@ .maxstack 2 IL_0048: ldloc.s V_9 IL_004a: unbox.any ""byte"" IL_004f: stloc.2 - IL_0050: brfalse.s IL_005e - IL_0052: br.s IL_0092 + IL_0050: brfalse.s IL_005c + IL_0052: br.s IL_0090 IL_0054: ldloc.2 - IL_0055: stloc.2 - IL_0056: ldloc.2 - IL_0057: ldc.i4.1 - IL_0058: beq.s IL_005c - IL_005a: br.s IL_005e - IL_005c: br.s IL_00bc - IL_005e: ldloc.0 - IL_005f: stloc.s V_9 - IL_0061: ldloc.s V_9 - IL_0063: isinst ""int"" - IL_0068: ldnull - IL_0069: cgt.un - IL_006b: dup - IL_006c: brtrue.s IL_0071 - IL_006e: ldc.i4.0 - IL_006f: br.s IL_0078 - IL_0071: ldloc.s V_9 - IL_0073: unbox.any ""int"" - IL_0078: stloc.1 - IL_0079: brfalse.s IL_007f - IL_007b: br.s IL_00a6 - IL_007d: br.s IL_00c9 - IL_007f: ldloc.0 - IL_0080: stloc.0 - IL_0081: br.s IL_00d8 - IL_0083: br.s IL_00e7 - IL_0085: ldstr ""int 1"" - IL_008a: call ""void System.Console.WriteLine(string)"" - IL_008f: nop - IL_0090: br.s IL_00e7 - IL_0092: ldloc.2 - IL_0093: stloc.3 - IL_0094: call ""bool C.P()"" - IL_0099: brtrue.s IL_009d - IL_009b: br.s IL_0054 - IL_009d: ldloc.3 - IL_009e: call ""void System.Console.WriteLine(int)"" - IL_00a3: nop - IL_00a4: br.s IL_00e7 - IL_00a6: ldloc.1 - IL_00a7: stloc.s V_4 - IL_00a9: call ""bool C.P()"" - IL_00ae: brtrue.s IL_00b2 - IL_00b0: br.s IL_007d - IL_00b2: ldloc.s V_4 - IL_00b4: call ""void System.Console.WriteLine(int)"" - IL_00b9: nop - IL_00ba: br.s IL_00e7 - IL_00bc: ldstr ""byte 1"" - IL_00c1: call ""void System.Console.WriteLine(string)"" - IL_00c6: nop - IL_00c7: br.s IL_00e7 - IL_00c9: ldloc.1 - IL_00ca: stloc.s V_5 - IL_00cc: br.s IL_00ce - IL_00ce: ldloc.s V_5 - IL_00d0: call ""void System.Console.WriteLine(int)"" - IL_00d5: nop - IL_00d6: br.s IL_00e7 - IL_00d8: ldloc.0 - IL_00d9: stloc.s V_6 - IL_00db: br.s IL_00dd - IL_00dd: ldloc.s V_6 - IL_00df: call ""void System.Console.WriteLine(object)"" - IL_00e4: nop - IL_00e5: br.s IL_00e7 - IL_00e7: ret + IL_0055: ldc.i4.1 + IL_0056: beq.s IL_005a + IL_0058: br.s IL_005c + IL_005a: br.s IL_00ba + IL_005c: ldloc.0 + IL_005d: stloc.s V_9 + IL_005f: ldloc.s V_9 + IL_0061: isinst ""int"" + IL_0066: ldnull + IL_0067: cgt.un + IL_0069: dup + IL_006a: brtrue.s IL_006f + IL_006c: ldc.i4.0 + IL_006d: br.s IL_0076 + IL_006f: ldloc.s V_9 + IL_0071: unbox.any ""int"" + IL_0076: stloc.1 + IL_0077: brfalse.s IL_007d + IL_0079: br.s IL_00a4 + IL_007b: br.s IL_00c7 + IL_007d: ldloc.0 + IL_007e: stloc.0 + IL_007f: br.s IL_00d6 + IL_0081: br.s IL_00e5 + IL_0083: ldstr ""int 1"" + IL_0088: call ""void System.Console.WriteLine(string)"" + IL_008d: nop + IL_008e: br.s IL_00e5 + IL_0090: ldloc.2 + IL_0091: stloc.3 + IL_0092: call ""bool C.P()"" + IL_0097: brtrue.s IL_009b + IL_0099: br.s IL_0054 + IL_009b: ldloc.3 + IL_009c: call ""void System.Console.WriteLine(int)"" + IL_00a1: nop + IL_00a2: br.s IL_00e5 + IL_00a4: ldloc.1 + IL_00a5: stloc.s V_4 + IL_00a7: call ""bool C.P()"" + IL_00ac: brtrue.s IL_00b0 + IL_00ae: br.s IL_007b + IL_00b0: ldloc.s V_4 + IL_00b2: call ""void System.Console.WriteLine(int)"" + IL_00b7: nop + IL_00b8: br.s IL_00e5 + IL_00ba: ldstr ""byte 1"" + IL_00bf: call ""void System.Console.WriteLine(string)"" + IL_00c4: nop + IL_00c5: br.s IL_00e5 + IL_00c7: ldloc.1 + IL_00c8: stloc.s V_5 + IL_00ca: br.s IL_00cc + IL_00cc: ldloc.s V_5 + IL_00ce: call ""void System.Console.WriteLine(int)"" + IL_00d3: nop + IL_00d4: br.s IL_00e5 + IL_00d6: ldloc.0 + IL_00d7: stloc.s V_6 + IL_00d9: br.s IL_00db + IL_00db: ldloc.s V_6 + IL_00dd: call ""void System.Console.WriteLine(object)"" + IL_00e2: nop + IL_00e3: br.s IL_00e5 + IL_00e5: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index 37ea07182ff..a4ab3e12e3d 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -2984,6 +2984,436 @@ class Student : Person { public double GPA; } "); } + [Fact, WorkItem(17090, "https://github.com/dotnet/roslyn/issues/17090"), WorkItem(19731, "https://github.com/dotnet/roslyn/issues/19731")] + public void SwitchWithConstantPattern() + { + string source = @" +using System; + +class Program +{ + static void Main(string[] args) + { + M1(); + M2(); + } + + static void M1() + { + switch + (1) + { + case 0 when true: + ; + case 1: + Console.Write(1); + break; + case 2: + ; + } + } + + static void M2() + { + switch + (nameof(M2)) + { + case nameof(M1) when true: + ; + case nameof(M2): + Console.Write(nameof(M2)); + break; + case nameof(Main): + ; + } + } +} +"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugExe); + c.VerifyDiagnostics(); + var verifier = CompileAndVerify(c, expectedOutput: "1M2"); + + verifier.VerifyIL(qualifiedMethodName: "Program.M1", sequencePoints: "Program.M1", source: source, +expectedIL: @"{ + // Code size 15 (0xf) + .maxstack 1 + .locals init (int V_0) + // sequence point: { + IL_0000: nop + // sequence point: switch ... (1 + IL_0001: ldc.i4.1 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + // sequence point: Console.Write(1); + IL_0005: ldc.i4.1 + IL_0006: call ""void System.Console.Write(int)"" + IL_000b: nop + // sequence point: break; + IL_000c: br.s IL_000e + // sequence point: } + IL_000e: ret +}"); + verifier.VerifyIL(qualifiedMethodName: "Program.M2", sequencePoints: "Program.M2", source: source, +expectedIL: @"{ + // Code size 23 (0x17) + .maxstack 1 + .locals init (string V_0) + // sequence point: { + IL_0000: nop + // sequence point: switch ... (nameof(M2) + IL_0001: ldstr ""M2"" + IL_0006: stloc.0 + IL_0007: br.s IL_0009 + // sequence point: Console.Write(nameof(M2)); + IL_0009: ldstr ""M2"" + IL_000e: call ""void System.Console.Write(string)"" + IL_0013: nop + // sequence point: break; + IL_0014: br.s IL_0016 + // sequence point: } + IL_0016: ret +}"); + + // Check the release code generation too. + c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe); + c.VerifyDiagnostics(); + verifier = CompileAndVerify(c, expectedOutput: "1M2"); + + verifier.VerifyIL("Program.M1", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + verifier.VerifyIL("Program.M2", +@"{ + // Code size 11 (0xb) + .maxstack 1 + IL_0000: ldstr ""M2"" + IL_0005: call ""void System.Console.Write(string)"" + IL_000a: ret +}"); + } + + [Fact, WorkItem(19734, "https://github.com/dotnet/roslyn/issues/19734")] + public void SwitchWithConstantGenericPattern_01() + { + string source = @" +using System; + +class Program +{ + static void Main(string[] args) + { + M1(); // 1 + M1(); // 2 + M2(); // 3 + M2(); // 4 + } + + static void M1() + { + switch (1) + { + case T t: + Console.Write(1); + break; + case int i: + Console.Write(2); + break; + } + } + + static void M2() + { + switch (nameof(M2)) + { + case T t: + Console.Write(3); + break; + case string s: + Console.Write(4); + break; + case null: + ; + } + } +} +"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_1); + c.VerifyDiagnostics(); + var verifier = CompileAndVerify(c, expectedOutput: "1234"); + + verifier.VerifyIL(qualifiedMethodName: "Program.M1", sequencePoints: "Program.M1", source: source, +expectedIL: @"{ + // Code size 80 (0x50) + .maxstack 2 + .locals init (T V_0, + int V_1, + T V_2, //t + int V_3, //i + int V_4, + object V_5, + T V_6) + // sequence point: { + IL_0000: nop + // sequence point: switch (1) + IL_0001: ldc.i4.1 + IL_0002: stloc.s V_4 + IL_0004: ldc.i4.1 + IL_0005: box ""int"" + IL_000a: stloc.s V_5 + IL_000c: ldloc.s V_5 + IL_000e: isinst ""T"" + IL_0013: ldnull + IL_0014: cgt.un + IL_0016: dup + IL_0017: brtrue.s IL_0025 + IL_0019: ldloca.s V_6 + IL_001b: initobj ""T"" + IL_0021: ldloc.s V_6 + IL_0023: br.s IL_002c + IL_0025: ldloc.s V_5 + IL_0027: unbox.any ""T"" + IL_002c: stloc.0 + IL_002d: brfalse.s IL_0031 + IL_002f: br.s IL_0035 + IL_0031: ldc.i4.1 + IL_0032: stloc.1 + IL_0033: br.s IL_0042 + // sequence point: + IL_0035: ldloc.0 + IL_0036: stloc.2 + IL_0037: br.s IL_0039 + // sequence point: Console.Write(1); + IL_0039: ldc.i4.1 + IL_003a: call ""void System.Console.Write(int)"" + IL_003f: nop + // sequence point: break; + IL_0040: br.s IL_004f + // sequence point: + IL_0042: ldloc.1 + IL_0043: stloc.3 + IL_0044: br.s IL_0046 + // sequence point: Console.Write(2); + IL_0046: ldc.i4.2 + IL_0047: call ""void System.Console.Write(int)"" + IL_004c: nop + // sequence point: break; + IL_004d: br.s IL_004f + // sequence point: } + IL_004f: ret +}"); + verifier.VerifyIL(qualifiedMethodName: "Program.M2", sequencePoints: "Program.M2", source: source, +expectedIL: @"{ + // Code size 87 (0x57) + .maxstack 2 + .locals init (T V_0, + string V_1, + T V_2, //t + string V_3, //s + string V_4, + object V_5, + T V_6) + // sequence point: { + IL_0000: nop + // sequence point: switch (nameof(M2)) + IL_0001: ldstr ""M2"" + IL_0006: stloc.s V_4 + IL_0008: ldstr ""M2"" + IL_000d: stloc.s V_5 + IL_000f: ldloc.s V_5 + IL_0011: isinst ""T"" + IL_0016: ldnull + IL_0017: cgt.un + IL_0019: dup + IL_001a: brtrue.s IL_0028 + IL_001c: ldloca.s V_6 + IL_001e: initobj ""T"" + IL_0024: ldloc.s V_6 + IL_0026: br.s IL_002f + IL_0028: ldloc.s V_5 + IL_002a: unbox.any ""T"" + IL_002f: stloc.0 + IL_0030: brfalse.s IL_0034 + IL_0032: br.s IL_003c + IL_0034: ldstr ""M2"" + IL_0039: stloc.1 + IL_003a: br.s IL_0049 + // sequence point: + IL_003c: ldloc.0 + IL_003d: stloc.2 + IL_003e: br.s IL_0040 + // sequence point: Console.Write(3); + IL_0040: ldc.i4.3 + IL_0041: call ""void System.Console.Write(int)"" + IL_0046: nop + // sequence point: break; + IL_0047: br.s IL_0056 + // sequence point: + IL_0049: ldloc.1 + IL_004a: stloc.3 + IL_004b: br.s IL_004d + // sequence point: Console.Write(4); + IL_004d: ldc.i4.4 + IL_004e: call ""void System.Console.Write(int)"" + IL_0053: nop + // sequence point: break; + IL_0054: br.s IL_0056 + // sequence point: } + IL_0056: ret +}"); + + // Check the release code generation too. + c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular7_1); + c.VerifyDiagnostics(); + verifier = CompileAndVerify(c, expectedOutput: "1234"); + + verifier.VerifyIL("Program.M1", +@"{ + // Code size 57 (0x39) + .maxstack 2 + .locals init (T V_0, + int V_1, + object V_2, + T V_3) + IL_0000: ldc.i4.1 + IL_0001: box ""int"" + IL_0006: stloc.2 + IL_0007: ldloc.2 + IL_0008: isinst ""T"" + IL_000d: ldnull + IL_000e: cgt.un + IL_0010: dup + IL_0011: brtrue.s IL_001e + IL_0013: ldloca.s V_3 + IL_0015: initobj ""T"" + IL_001b: ldloc.3 + IL_001c: br.s IL_0024 + IL_001e: ldloc.2 + IL_001f: unbox.any ""T"" + IL_0024: stloc.0 + IL_0025: brtrue.s IL_002b + IL_0027: ldc.i4.1 + IL_0028: stloc.1 + IL_0029: br.s IL_0032 + IL_002b: ldc.i4.1 + IL_002c: call ""void System.Console.Write(int)"" + IL_0031: ret + IL_0032: ldc.i4.2 + IL_0033: call ""void System.Console.Write(int)"" + IL_0038: ret +}"); + verifier.VerifyIL("Program.M2", +@"{ + // Code size 60 (0x3c) + .maxstack 2 + .locals init (T V_0, + string V_1, + object V_2, + T V_3) + IL_0000: ldstr ""M2"" + IL_0005: stloc.2 + IL_0006: ldloc.2 + IL_0007: isinst ""T"" + IL_000c: ldnull + IL_000d: cgt.un + IL_000f: dup + IL_0010: brtrue.s IL_001d + IL_0012: ldloca.s V_3 + IL_0014: initobj ""T"" + IL_001a: ldloc.3 + IL_001b: br.s IL_0023 + IL_001d: ldloc.2 + IL_001e: unbox.any ""T"" + IL_0023: stloc.0 + IL_0024: brtrue.s IL_002e + IL_0026: ldstr ""M2"" + IL_002b: stloc.1 + IL_002c: br.s IL_0035 + IL_002e: ldc.i4.3 + IL_002f: call ""void System.Console.Write(int)"" + IL_0034: ret + IL_0035: ldc.i4.4 + IL_0036: call ""void System.Console.Write(int)"" + IL_003b: ret +}"); + } + + [Fact, WorkItem(19734, "https://github.com/dotnet/roslyn/issues/19734")] + public void SwitchWithConstantGenericPattern_02() + { + string source = @" +using System; + +class Program +{ + static void Main(string[] args) + { + M2(); // 6 + M2(); // 6 + } + + static void M2() + { + const string x = null; + switch (x) + { + case T t: + ; + case string s: + ; + case null: + Console.Write(6); + break; + } + } +} +"; + var c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_1); + c.VerifyDiagnostics(); + var verifier = CompileAndVerify(c, expectedOutput: "66"); + + verifier.VerifyIL(qualifiedMethodName: "Program.M2", sequencePoints: "Program.M2", source: source, +expectedIL: @"{ + // Code size 15 (0xf) + .maxstack 1 + .locals init (T V_0, //t + string V_1, //s + string V_2) + // sequence point: { + IL_0000: nop + // sequence point: switch (x) + IL_0001: ldnull + IL_0002: stloc.2 + IL_0003: br.s IL_0005 + // sequence point: Console.Write(6); + IL_0005: ldc.i4.6 + IL_0006: call ""void System.Console.Write(int)"" + IL_000b: nop + // sequence point: break; + IL_000c: br.s IL_000e + // sequence point: } + IL_000e: ret +}"); + + // Check the release code generation too. + c = CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular7_1); + c.VerifyDiagnostics(); + verifier = CompileAndVerify(c, expectedOutput: "66"); + + verifier.VerifyIL("Program.M2", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.6 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + } + #endregion #region DoStatement -- GitLab