diff --git a/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs b/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs index 4496196c67e24..3fb0dbc980ae0 100644 --- a/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SubsumptionDiagnosticBuilder.cs @@ -2,11 +2,11 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { - /// /// Helper class for binding the pattern switch statement. It helps compute which labels /// are subsumed and/or reachable. The strategy, implemented in , @@ -15,12 +15,12 @@ namespace Microsoft.CodeAnalysis.CSharp /// If it is not subsumed and there is no guard expression, we then add it to the decision /// tree. /// - internal class SubsumptionDiagnosticBuilder : DecisionTreeBuilder + internal sealed class SubsumptionDiagnosticBuilder : DecisionTreeBuilder { private readonly DecisionTree _subsumptionTree; internal SubsumptionDiagnosticBuilder(Symbol enclosingSymbol, - SyntaxNode syntax, + SwitchStatementSyntax syntax, Conversions conversions, BoundExpression expression) : base(enclosingSymbol, syntax, conversions) @@ -52,7 +52,6 @@ internal bool AddLabel(BoundPatternSwitchLabel label, DiagnosticBag diagnostics, { // For purposes of subsumption, we do not take into consideration the value // of the input expression. Therefore we consider null possible if the type permits. - Syntax = label.Syntax; var inputCouldBeNull = _subsumptionTree.Type.CanContainNull(); var subsumedErrorCode = CheckSubsumed(label.Pattern, _subsumptionTree, inputCouldBeNull: inputCouldBeNull); if (subsumedErrorCode != 0 && subsumedErrorCode != ErrorCode.ERR_NoImplicitConvCast) diff --git a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs index 703dffcf48a03..cf099b336a8a7 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/DecisionTreeBuilder.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; @@ -26,30 +27,32 @@ internal abstract class DecisionTreeBuilder protected readonly Symbol _enclosingSymbol; protected readonly Conversions _conversions; protected HashSet _useSiteDiagnostics = new HashSet(); + protected readonly SwitchStatementSyntax _switchSyntax; private Dictionary localByType = new Dictionary(); protected DecisionTreeBuilder( Symbol enclosingSymbol, - SyntaxNode syntax, + SwitchStatementSyntax switchSyntax, Conversions conversions) { - this._enclosingSymbol = enclosingSymbol; - this.Syntax = syntax; - this._conversions = conversions; + _enclosingSymbol = enclosingSymbol; + _switchSyntax = switchSyntax; + _conversions = conversions; } - protected SyntaxNode Syntax { private get; set; } - - private LocalSymbol PatternMatchingTemp(TypeSymbol type) + private BoundLocal GetBoundPatternMatchingLocal(TypeSymbol type) { - LocalSymbol temp; - if (!localByType.TryGetValue(type, out temp)) + // All synthesized pattern matching locals are associated with the Switch statement syntax node. + // Their ordinals are zero. + // EnC local slot variable matching logic find the right slot based on the type of the local. + + if (!localByType.TryGetValue(type, out var localSymbol)) { - temp = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax); - localByType.Add(type, temp); + localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.SwitchCasePatternMatching, _switchSyntax); + localByType.Add(type, localSymbol); } - return temp; + return new BoundLocal(_switchSyntax, localSymbol, null, type); } /// @@ -59,13 +62,14 @@ protected DecisionTree CreateEmptyDecisionTree(BoundExpression expression) { var type = expression.Type; - LocalSymbol temp = null; + LocalSymbol localSymbol = null; if (expression.ConstantValue == null) { // Unless it is a constant, the decision tree acts on a copy of the input expression. // We create a temp to represent that copy. Lowering will assign into this temp. - temp = PatternMatchingTemp(type); - expression = new BoundLocal(expression.Syntax, temp, null, type); + var local = GetBoundPatternMatchingLocal(type); + expression = local; + localSymbol = local.LocalSymbol; } if (type.CanContainNull() || type.SpecialType == SpecialType.None) @@ -74,12 +78,12 @@ protected DecisionTree CreateEmptyDecisionTree(BoundExpression expression) // Note that, for the purpose of the decision tree (and subsumption), we // ignore the fact that the input may be a constant, and therefore always // or never null. - return new DecisionTree.ByType(expression, type, temp); + return new DecisionTree.ByType(expression, type, localSymbol); } else { // If it is a (e.g. builtin) value type, we can switch on its (constant) values. - return new DecisionTree.ByValue(expression, type, temp); + return new DecisionTree.ByValue(expression, type, localSymbol); } } @@ -283,10 +287,9 @@ private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern if (forType == null) { var type = value.Value.Type; - var localSymbol = PatternMatchingTemp(type); - var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); - forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); - byType.TypeAndDecision.Add(new KeyValuePair(value.Value.Type, forType)); + var narrowedExpression = GetBoundPatternMatchingLocal(type); + forType = new DecisionTree.ByValue(narrowedExpression, type.TupleUnderlyingTypeOrSelf(), narrowedExpression.LocalSymbol); + byType.TypeAndDecision.Add(new KeyValuePair(type, forType)); } return AddByValue(forType, value, makeDecision); @@ -384,11 +387,10 @@ private DecisionTree AddByType(DecisionTree.ByType byType, TypeSymbol type, Deci if (result == null) { - var localSymbol = PatternMatchingTemp(type); - var expression = new BoundLocal(Syntax, localSymbol, null, type); + var expression = GetBoundPatternMatchingLocal(type); result = makeDecision(expression, type); Debug.Assert(result.Temp == null); - result.Temp = localSymbol; + result.Temp = expression.LocalSymbol; byType.TypeAndDecision.Add(new KeyValuePair(type, result)); } diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs index f40172c021fbe..392d6040b973d 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs @@ -1535,7 +1535,11 @@ private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId lo var syntax = local.GetDeclaratorSyntax(); int syntaxOffset = _method.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); - int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset); + // Synthesized locals emitted for switch case patterns are all associated with the switch statement + // and have distinct types. We use theier types to match them, not the ordinal as the ordinal might + // change if switch cases are reordered. + int ordinal = (localKind != SynthesizedLocalKind.SwitchCasePatternMatching) ? + _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset) : 0; // user-defined locals should have 0 ordinal: Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs index c8f353222bbab..1eede24c39954 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -38,14 +39,14 @@ private class PatternSwitchLocalRewriter : DecisionTreeBuilder private ArrayBuilder _loweredDecisionTree = ArrayBuilder.GetInstance(); private PatternSwitchLocalRewriter(LocalRewriter localRewriter, BoundPatternSwitchStatement node) - : base(localRewriter._factory.CurrentMethod, node.Syntax, localRewriter._factory.Compilation.Conversions) + : base(localRewriter._factory.CurrentMethod, (SwitchStatementSyntax)node.Syntax, localRewriter._factory.Compilation.Conversions) { this._localRewriter = localRewriter; this._factory = localRewriter._factory; this._factory.Syntax = node.Syntax; foreach (var section in node.SwitchSections) { - _switchSections.Add((SyntaxNode)section.Syntax, ArrayBuilder.GetInstance()); + _switchSections.Add(section.Syntax, ArrayBuilder.GetInstance()); } } @@ -146,7 +147,7 @@ private DecisionTree LowerToDecisionTree( SyntaxNode defaultSection = null; foreach (var section in node.SwitchSections) { - var sectionSyntax = (SyntaxNode)section.Syntax; + var sectionSyntax = section.Syntax; foreach (var label in section.SwitchLabels) { var loweredLabel = LowerSwitchLabel(label); @@ -164,7 +165,6 @@ private DecisionTree LowerToDecisionTree( } else { - Syntax = label.Syntax; AddToDecisionTree(loweredDecisionTree, sectionSyntax, loweredLabel); } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 3472ef5b26ed6..148ef28e9aaaa 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -9433,7 +9433,7 @@ .locals init (object V_0, - + diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index 2ceeac1661b2f..3ace3ef622237 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.DiaSymReader.Tools; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -2350,6 +2351,298 @@ .locals init (int V_0) }"); } + [Fact] + public void Switch_Patterns() + { + var source = @" +using static System.Console; +class C +{ + static object F() => 1; + static bool P() => false; + + static void M() + { + switch (F()) + { + case 1: WriteLine(""int 1""); break; + case byte b when P(): WriteLine(b); break; + case int i when P(): WriteLine(i); break; + case (byte)1: WriteLine(""byte 1""); break; + case int j: WriteLine(j); break; + case object o: WriteLine(o); break; + } + } +}"; + var compilation0 = CreateStandardCompilation(source, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source); + + var v0 = CompileAndVerify(compilation0); + + v0.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + +", options: PdbToXmlOptions.ExcludeScopes | PdbToXmlOptions.ExcludeSequencePoints); + + v0.VerifyIL("C.M", @" +{ + // Code size 235 (0xeb) + .maxstack 2 + .locals init (object V_0, + int V_1, + byte V_2, + byte V_3, //b + int V_4, //i + int V_5, //j + object V_6, //o + object V_7, + int? V_8, + byte? V_9) + IL_0000: nop + IL_0001: call ""object C.F()"" + IL_0006: stloc.s V_7 + IL_0008: ldloc.s V_7 + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: brtrue.s IL_0010 + IL_000e: br.s IL_0086 + IL_0010: ldloc.0 + IL_0011: isinst ""int?"" + IL_0016: unbox.any ""int?"" + IL_001b: stloc.s V_8 + IL_001d: ldloca.s V_8 + IL_001f: call ""int int?.GetValueOrDefault()"" + IL_0024: stloc.1 + IL_0025: ldloca.s V_8 + IL_0027: call ""bool int?.HasValue.get"" + IL_002c: brfalse.s IL_0036 + IL_002e: ldloc.1 + IL_002f: ldc.i4.1 + IL_0030: beq.s IL_0034 + IL_0032: br.s IL_0036 + IL_0034: br.s IL_0088 + IL_0036: ldloc.0 + IL_0037: isinst ""byte?"" + IL_003c: unbox.any ""byte?"" + IL_0041: stloc.s V_9 + IL_0043: ldloca.s V_9 + IL_0045: call ""byte byte?.GetValueOrDefault()"" + IL_004a: stloc.2 + IL_004b: ldloca.s V_9 + IL_004d: call ""bool byte?.HasValue.get"" + IL_0052: brfalse.s IL_0060 + IL_0054: br.s IL_0095 + IL_0056: ldloc.2 + IL_0057: stloc.2 + IL_0058: ldloc.2 + IL_0059: ldc.i4.1 + IL_005a: beq.s IL_005e + IL_005c: br.s IL_0060 + IL_005e: br.s IL_00bf + IL_0060: ldloc.0 + IL_0061: isinst ""int?"" + IL_0066: unbox.any ""int?"" + IL_006b: stloc.s V_8 + IL_006d: ldloca.s V_8 + IL_006f: call ""int int?.GetValueOrDefault()"" + IL_0074: stloc.1 + IL_0075: ldloca.s V_8 + IL_0077: call ""bool int?.HasValue.get"" + IL_007c: brfalse.s IL_0082 + IL_007e: br.s IL_00a9 + IL_0080: br.s IL_00cc + IL_0082: ldloc.0 + IL_0083: stloc.0 + IL_0084: br.s IL_00db + IL_0086: br.s IL_00ea + IL_0088: ldstr ""int 1"" + IL_008d: call ""void System.Console.WriteLine(string)"" + IL_0092: nop + IL_0093: br.s IL_00ea + IL_0095: ldloc.2 + IL_0096: stloc.3 + IL_0097: call ""bool C.P()"" + IL_009c: brtrue.s IL_00a0 + IL_009e: br.s IL_0056 + IL_00a0: ldloc.3 + IL_00a1: call ""void System.Console.WriteLine(int)"" + IL_00a6: nop + IL_00a7: br.s IL_00ea + IL_00a9: ldloc.1 + IL_00aa: stloc.s V_4 + IL_00ac: call ""bool C.P()"" + IL_00b1: brtrue.s IL_00b5 + IL_00b3: br.s IL_0080 + IL_00b5: ldloc.s V_4 + IL_00b7: call ""void System.Console.WriteLine(int)"" + IL_00bc: nop + IL_00bd: br.s IL_00ea + IL_00bf: ldstr ""byte 1"" + IL_00c4: call ""void System.Console.WriteLine(string)"" + IL_00c9: nop + IL_00ca: br.s IL_00ea + IL_00cc: ldloc.1 + IL_00cd: stloc.s V_5 + IL_00cf: br.s IL_00d1 + IL_00d1: ldloc.s V_5 + IL_00d3: call ""void System.Console.WriteLine(int)"" + IL_00d8: nop + IL_00d9: br.s IL_00ea + IL_00db: ldloc.0 + IL_00dc: stloc.s V_6 + IL_00de: br.s IL_00e0 + IL_00e0: ldloc.s V_6 + IL_00e2: call ""void System.Console.WriteLine(object)"" + IL_00e7: nop + IL_00e8: br.s IL_00ea + IL_00ea: ret +}"); + + var methodData0 = v0.TestData.GetMethodData("C.M"); + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + var generation0 = EmitBaseline.CreateInitialBaseline(ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData), methodData0.EncDebugInfoProvider()); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(new SemanticEdit(SemanticEditKind.Update, method0, method1, GetEquivalentNodesMap(method1, method0), preserveLocalVariables: true))); + + diff1.VerifyIL("C.M", @" +{ + // Code size 235 (0xeb) + .maxstack 2 + .locals init (object V_0, + int V_1, + byte V_2, + byte V_3, //b + int V_4, //i + int V_5, //j + object V_6, //o + object V_7, + [unchanged] V_8, + [unchanged] V_9, + int? V_10, + byte? V_11) + IL_0000: nop + IL_0001: call ""object C.F()"" + IL_0006: stloc.s V_7 + IL_0008: ldloc.s V_7 + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: brtrue.s IL_0010 + IL_000e: br.s IL_0086 + IL_0010: ldloc.0 + IL_0011: isinst ""int?"" + IL_0016: unbox.any ""int?"" + IL_001b: stloc.s V_10 + IL_001d: ldloca.s V_10 + IL_001f: call ""int int?.GetValueOrDefault()"" + IL_0024: stloc.1 + IL_0025: ldloca.s V_10 + IL_0027: call ""bool int?.HasValue.get"" + IL_002c: brfalse.s IL_0036 + IL_002e: ldloc.1 + IL_002f: ldc.i4.1 + IL_0030: beq.s IL_0034 + IL_0032: br.s IL_0036 + IL_0034: br.s IL_0088 + IL_0036: ldloc.0 + IL_0037: isinst ""byte?"" + IL_003c: unbox.any ""byte?"" + IL_0041: stloc.s V_11 + IL_0043: ldloca.s V_11 + IL_0045: call ""byte byte?.GetValueOrDefault()"" + IL_004a: stloc.2 + IL_004b: ldloca.s V_11 + IL_004d: call ""bool byte?.HasValue.get"" + IL_0052: brfalse.s IL_0060 + IL_0054: br.s IL_0095 + IL_0056: ldloc.2 + IL_0057: stloc.2 + IL_0058: ldloc.2 + IL_0059: ldc.i4.1 + IL_005a: beq.s IL_005e + IL_005c: br.s IL_0060 + IL_005e: br.s IL_00bf + IL_0060: ldloc.0 + IL_0061: isinst ""int?"" + IL_0066: unbox.any ""int?"" + IL_006b: stloc.s V_10 + IL_006d: ldloca.s V_10 + IL_006f: call ""int int?.GetValueOrDefault()"" + IL_0074: stloc.1 + IL_0075: ldloca.s V_10 + IL_0077: call ""bool int?.HasValue.get"" + IL_007c: brfalse.s IL_0082 + IL_007e: br.s IL_00a9 + IL_0080: br.s IL_00cc + IL_0082: ldloc.0 + IL_0083: stloc.0 + IL_0084: br.s IL_00db + IL_0086: br.s IL_00ea + IL_0088: ldstr ""int 1"" + IL_008d: call ""void System.Console.WriteLine(string)"" + IL_0092: nop + IL_0093: br.s IL_00ea + IL_0095: ldloc.2 + IL_0096: stloc.3 + IL_0097: call ""bool C.P()"" + IL_009c: brtrue.s IL_00a0 + IL_009e: br.s IL_0056 + IL_00a0: ldloc.3 + IL_00a1: call ""void System.Console.WriteLine(int)"" + IL_00a6: nop + IL_00a7: br.s IL_00ea + IL_00a9: ldloc.1 + IL_00aa: stloc.s V_4 + IL_00ac: call ""bool C.P()"" + IL_00b1: brtrue.s IL_00b5 + IL_00b3: br.s IL_0080 + IL_00b5: ldloc.s V_4 + IL_00b7: call ""void System.Console.WriteLine(int)"" + IL_00bc: nop + IL_00bd: br.s IL_00ea + IL_00bf: ldstr ""byte 1"" + IL_00c4: call ""void System.Console.WriteLine(string)"" + IL_00c9: nop + IL_00ca: br.s IL_00ea + IL_00cc: ldloc.1 + IL_00cd: stloc.s V_5 + IL_00cf: br.s IL_00d1 + IL_00d1: ldloc.s V_5 + IL_00d3: call ""void System.Console.WriteLine(int)"" + IL_00d8: nop + IL_00d9: br.s IL_00ea + IL_00db: ldloc.0 + IL_00dc: stloc.s V_6 + IL_00de: br.s IL_00e0 + IL_00e0: ldloc.s V_6 + IL_00e2: call ""void System.Console.WriteLine(object)"" + IL_00e7: nop + IL_00e8: br.s IL_00ea + IL_00ea: ret +} +"); + } + [Fact] public void If() { diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index dad8fbff4d285..d0f4515e2a48e 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -2852,8 +2852,8 @@ class Student : Person { public double GPA; } - - + + @@ -2943,8 +2943,8 @@ class Student : Person { public double GPA; } - - + + @@ -6885,11 +6885,11 @@ .locals init (object V_0, - + - + diff --git a/src/Compilers/Core/Portable/SynthesizedLocalKind.cs b/src/Compilers/Core/Portable/SynthesizedLocalKind.cs index aba47777cff15..8b36f942c30bf 100644 --- a/src/Compilers/Core/Portable/SynthesizedLocalKind.cs +++ b/src/Compilers/Core/Portable/SynthesizedLocalKind.cs @@ -207,7 +207,7 @@ internal enum SynthesizedLocalKind /// Temp created for pattern matching by type. This holds the value of an input value provisionally /// converted to the type against which it is being matched. /// - PatternMatching = 35, + SwitchCasePatternMatching = 35, /// /// All values have to be less than or equal to