From 19da7ce27adc9a32def6b535639441d5cda72e21 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 15 Jul 2019 14:40:38 -0700 Subject: [PATCH] Fix EnC debug information emitted for patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update calculation of syntax offset to account for a new case when a node (a switch expression) that is associated with a variable, closure or lambda may share start offset with other node of the same kind (`expr switch { … } switch { … }`). Use the offset of the `switch` keyword instead of the starting offset of the expression to disambiguate. Assign ordinals to variables synthesized for storing pattern values across cases. This is required to support complex patterns since we can no longer rely on the type of these variables to be distinct. This will require follow up in the IDE to disallow updating/adding/reordering the case clauses of switch expression which there an active statement is present within the switch statement. If the cases are unmodified the compiler guarantees that the order in which the synthesized variables are generated remains the same, so we can map the variables using their ordinal. Mark all variables synthesized during lowering of switch expression as short-lived. Their lifespan is limited to the switch expression, which does not include a sequence point. Disallow editing methods that contain switch expression. This is necessary until bugs https://github.com/dotnet/roslyn/issues/37232, https://github.com/dotnet/roslyn/issues/37237 are fixed. --- .../CSharp/Portable/CodeGen/CodeGenerator.cs | 2 +- .../CSharp/Portable/CodeGen/EmitStatement.cs | 9 +- .../CSharpLambdaSyntaxFacts.cs | 16 +- .../LambdaRewriter/LambdaRewriter.Analysis.cs | 2 +- .../Lowering/LambdaRewriter/LambdaRewriter.cs | 2 +- ...Rewriter_BasePatternSwitchLocalRewriter.cs | 30 +- .../LocalRewriter_IsPatternOperator.cs | 8 +- .../LocalRewriter_PatternSwitchStatement.cs | 22 +- .../LocalRewriter/LocalRewriter_Patterns.cs | 31 +- .../LocalRewriter_SwitchExpression.cs | 10 +- .../Portable/Lowering/SpillSequenceSpiller.cs | 1 - .../MethodToStateMachineRewriter.cs | 2 +- .../StateMachineRewriter.cs | 2 +- .../CSharp/Portable/Syntax/LambdaUtilities.cs | 10 + .../CSharp/Test/Emit/PDB/PDBTests.cs | 1855 +++++++++++++++-- .../EncVariableSlotAllocator.cs | 12 +- .../Emit/EditAndContinue/LambdaSyntaxFacts.cs | 6 + .../VisualBasicLambdaSyntaxFacts.vb | 4 + 18 files changed, 1739 insertions(+), 285 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs index cffc7949283de..120634338bcb9 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs @@ -157,7 +157,7 @@ private LocalDefinition LazyReturnTemp var bodySyntax = _methodBodySyntaxOpt; if (_ilEmitStyle == ILEmitStyle.Debug && bodySyntax != null) { - int syntaxOffset = _method.CalculateLocalSyntaxOffset(bodySyntax.SpanStart, bodySyntax.SyntaxTree); + int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(bodySyntax), bodySyntax.SyntaxTree); var localSymbol = new SynthesizedLocal(_method, _method.ReturnTypeWithAnnotations, SynthesizedLocalKind.FunctionReturnValue, bodySyntax); result = _builder.LocalSlotManager.DeclareLocal( diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs index e5ef0f94da3ef..ea1d28693f917 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs @@ -1435,13 +1435,8 @@ private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId lo if (_ilEmitStyle == ILEmitStyle.Debug) { var syntax = local.GetDeclaratorSyntax(); - int syntaxOffset = _method.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); - - // Synthesized locals emitted for switch case patterns are all associated with the switch statement - // and have distinct types. We use their 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; + int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree); + int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset); // user-defined locals should have 0 ordinal: Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined); diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpLambdaSyntaxFacts.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpLambdaSyntaxFacts.cs index 8726ba5731b77..50b3048fe16eb 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpLambdaSyntaxFacts.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpLambdaSyntaxFacts.cs @@ -13,16 +13,12 @@ private CSharpLambdaSyntaxFacts() } public override SyntaxNode GetLambda(SyntaxNode lambdaOrLambdaBodySyntax) - { - return LambdaUtilities.GetLambda(lambdaOrLambdaBodySyntax); - } + => LambdaUtilities.GetLambda(lambdaOrLambdaBodySyntax); - public override SyntaxNode TryGetCorrespondingLambdaBody( - SyntaxNode previousLambdaSyntax, - SyntaxNode lambdaOrLambdaBodySyntax) - { - return LambdaUtilities.TryGetCorrespondingLambdaBody( - lambdaOrLambdaBodySyntax, previousLambdaSyntax); - } + public override SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode previousLambdaSyntax, SyntaxNode lambdaOrLambdaBodySyntax) + => LambdaUtilities.TryGetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax, previousLambdaSyntax); + + public override int GetDeclaratorPosition(SyntaxNode node) + => LambdaUtilities.GetDeclaratorPosition(node); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs index b85a9206fb1c8..ba18003f89fc1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.Analysis.cs @@ -535,7 +535,7 @@ internal DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder closureId = new DebugId(closureDebugInfo.Count, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); } - int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree); + int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree); closureDebugInfo.Add(new ClosureDebugInfo(syntaxOffset, closureId)); return closureId; diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs index cd7217a3418cc..f8a018fd0ce01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs @@ -1388,7 +1388,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos lambdaId = new DebugId(_lambdaDebugInfoBuilder.Count, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal); } - int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(lambdaOrLambdaBodySyntax.SpanStart, lambdaOrLambdaBodySyntax.SyntaxTree); + int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(lambdaOrLambdaBodySyntax), lambdaOrLambdaBodySyntax.SyntaxTree); _lambdaDebugInfoBuilder.Add(new LambdaDebugInfo(syntaxOffset, lambdaId, closureOrdinal)); return lambdaId; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs index a261a0e3fe16e..016dda05fc8fd 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs @@ -17,7 +17,7 @@ internal partial class LocalRewriter /// /// A common base class for lowering the pattern switch statement and the pattern switch expression. /// - private class BaseSwitchLocalRewriter : PatternLocalRewriter + private abstract class BaseSwitchLocalRewriter : PatternLocalRewriter { /// /// Map from switch section's syntax to the lowered code for the section. The code for a section @@ -37,27 +37,19 @@ private class BaseSwitchLocalRewriter : PatternLocalRewriter /// private readonly PooledDictionary _dagNodeLabels = PooledDictionary.GetInstance(); - /// - /// True if we are translating a switch statement. This affects sequence points (a when clause gets - /// a sequence point in a switch statement, but not in a switch expression). - /// - private readonly bool _isSwitchStatement; - protected BaseSwitchLocalRewriter( SyntaxNode node, LocalRewriter localRewriter, - ImmutableArray arms, - bool isSwitchStatement) + ImmutableArray arms) : base(node, localRewriter) { - this._isSwitchStatement = isSwitchStatement; foreach (var arm in arms) { var armBuilder = ArrayBuilder.GetInstance(); // We start each switch block of a switch statement with a hidden sequence point so that // we do not appear to be in the previous switch block when we begin. - if (isSwitchStatement) + if (IsSwitchStatement) armBuilder.Add(_factory.HiddenSequencePoint()); _switchArms.Add(arm, armBuilder); @@ -289,7 +281,7 @@ protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput( // In a switch statement, there is a hidden sequence point after evaluating the input at the start of // the code to handle the decision dag. This is necessary so that jumps back from a `when` clause into // the decision dag do not appear to jump back up to the enclosing construct. - if (_isSwitchStatement) + if (IsSwitchStatement) result.Add(_factory.HiddenSequencePoint()); return decisionDag; @@ -303,9 +295,9 @@ protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput( Debug.Assert(this._loweredDecisionDag.IsEmpty()); ComputeLabelSet(decisionDag); LowerDecisionDagCore(decisionDag); - ImmutableArray loweredDag = this._loweredDecisionDag.ToImmutableAndFree(); - ImmutableDictionary> switchSections = this._switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree()); - this._switchArms.Clear(); + ImmutableArray loweredDag = _loweredDecisionDag.ToImmutableAndFree(); + var switchSections = _switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree()); + _switchArms.Clear(); return (loweredDag, switchSections); } @@ -343,7 +335,7 @@ private void LowerDecisionDagCore(BoundDecisionDag decisionDag) continue; } - if (this._dagNodeLabels.TryGetValue(node, out LabelSymbol label)) + if (_dagNodeLabels.TryGetValue(node, out LabelSymbol label)) { _loweredDecisionDag.Add(_factory.Label(label)); } @@ -581,7 +573,7 @@ private void LowerWhenClause(BoundWhenDecisionDagNode whenClause) BoundStatement conditionalGoto = _factory.ConditionalGoto(_localRewriter.VisitExpression(whenClause.WhenExpression), trueLabel, jumpIfTrue: true); // Only add instrumentation (such as a sequence point) if the node is not compiler-generated. - if (_isSwitchStatement && !whenClause.WhenExpression.WasCompilerGenerated && _localRewriter.Instrument) + if (IsSwitchStatement && !whenClause.WhenExpression.WasCompilerGenerated && _localRewriter.Instrument) { conditionalGoto = _localRewriter._instrumenter.InstrumentSwitchWhenClauseConditionalGotoBody(whenClause.WhenExpression, conditionalGoto); } @@ -592,7 +584,7 @@ private void LowerWhenClause(BoundWhenDecisionDagNode whenClause) // We hide the jump back into the decision dag, as it is not logically part of the when clause BoundStatement jump = _factory.Goto(GetDagNodeLabel(whenFalse)); - sectionBuilder.Add(_isSwitchStatement ? _factory.HiddenSequencePoint(jump) : jump); + sectionBuilder.Add(IsSwitchStatement ? _factory.HiddenSequencePoint(jump) : jump); } else { @@ -618,7 +610,7 @@ private void LowerDecisionDagNode(BoundDecisionDagNode node, BoundDecisionDagNod // We add a hidden sequence point after the evaluation's side-effect, which may be a call out // to user code such as `Deconstruct` or a property get, to permit edit-and-continue to // synchronize on changes. - if (_isSwitchStatement) + if (IsSwitchStatement) _loweredDecisionDag.Add(_factory.HiddenSequencePoint()); if (nextNode != evaluationNode.Next) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index 6f4a12891d2d2..8349b648b8a93 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -18,7 +18,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node return result; } - private class IsPatternExpressionLocalRewriter : PatternLocalRewriter + private sealed class IsPatternExpressionLocalRewriter : PatternLocalRewriter { /// /// Accumulates side-effects that come before the next conjunct. @@ -35,10 +35,12 @@ private class IsPatternExpressionLocalRewriter : PatternLocalRewriter public IsPatternExpressionLocalRewriter(SyntaxNode node, LocalRewriter localRewriter) : base(node, localRewriter) { - this._conjunctBuilder = ArrayBuilder.GetInstance(); - this._sideEffectBuilder = ArrayBuilder.GetInstance(); + _conjunctBuilder = ArrayBuilder.GetInstance(); + _sideEffectBuilder = ArrayBuilder.GetInstance(); } + protected override bool IsSwitchStatement => false; + public new void Free() { _conjunctBuilder.Free(); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs index 9011acf3a6da1..ea40c6a50ef16 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PatternSwitchStatement.cs @@ -16,24 +16,26 @@ internal partial class LocalRewriter { public override BoundNode VisitSwitchStatement(BoundSwitchStatement node) { - return SwitchLocalRewriter.Rewrite(this, node); + return SwitchStatementLocalRewriter.Rewrite(this, node); } - private class SwitchLocalRewriter : BaseSwitchLocalRewriter + private sealed class SwitchStatementLocalRewriter : BaseSwitchLocalRewriter { + /// + /// A map from section syntax to the first label in that section. + /// + private readonly Dictionary _sectionLabels = PooledDictionary.GetInstance(); + + protected override bool IsSwitchStatement => true; + public static BoundStatement Rewrite(LocalRewriter localRewriter, BoundSwitchStatement node) { - var rewriter = new SwitchLocalRewriter(node, localRewriter); + var rewriter = new SwitchStatementLocalRewriter(node, localRewriter); BoundStatement result = rewriter.LowerSwitchStatement(node); rewriter.Free(); return result; } - /// - /// A map from section syntax to the first label in that section. - /// - private Dictionary _sectionLabels = PooledDictionary.GetInstance(); - /// /// We revise the returned label for a leaf so that all leaves in the same switch section are given the same label. /// This enables the switch emitter to produce better code. @@ -62,8 +64,8 @@ protected override LabelSymbol GetDagNodeLabel(BoundDecisionDagNode dag) return result; } - private SwitchLocalRewriter(BoundSwitchStatement node, LocalRewriter localRewriter) - : base(node.Syntax, localRewriter, node.SwitchSections.SelectAsArray(section => section.Syntax), isSwitchStatement: true) + private SwitchStatementLocalRewriter(BoundSwitchStatement node, LocalRewriter localRewriter) + : base(node.Syntax, localRewriter, node.SwitchSections.SelectAsArray(section => section.Syntax)) { } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs index b462b1cbe0e48..663ef9b172bce 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs @@ -16,7 +16,7 @@ internal sealed partial class LocalRewriter /// /// A common base class for lowering constructs that use pattern-matching. /// - private class PatternLocalRewriter + private abstract class PatternLocalRewriter { protected readonly LocalRewriter _localRewriter; protected readonly SyntheticBoundNodeFactory _factory; @@ -24,27 +24,39 @@ private class PatternLocalRewriter public PatternLocalRewriter(SyntaxNode node, LocalRewriter localRewriter) { - this._localRewriter = localRewriter; - this._factory = localRewriter._factory; - this._tempAllocator = new DagTempAllocator(_factory, node); + _localRewriter = localRewriter; + _factory = localRewriter._factory; + _tempAllocator = new DagTempAllocator(_factory, node, IsSwitchStatement); } + /// + /// True if this is a rewriter for a switch statement. This affects + /// - sequence points + /// When clause gets a sequence point in a switch statement, but not in a switch expression. + /// - synthesized local variable kind + /// The temp variables must be long lived in a switch statement since their lifetime spans across sequence points. + /// + protected abstract bool IsSwitchStatement { get; } + public void Free() { _tempAllocator.Free(); } - public class DagTempAllocator + public sealed class DagTempAllocator { private readonly SyntheticBoundNodeFactory _factory; private readonly PooledDictionary _map = PooledDictionary.GetInstance(); private readonly ArrayBuilder _temps = ArrayBuilder.GetInstance(); private readonly SyntaxNode _node; - public DagTempAllocator(SyntheticBoundNodeFactory factory, SyntaxNode node) + private readonly bool _isSwitchStatement; + + public DagTempAllocator(SyntheticBoundNodeFactory factory, SyntaxNode node, bool isSwitchStatement) { - this._factory = factory; - this._node = node; + _factory = factory; + _node = node; + _isSwitchStatement = isSwitchStatement; } public void Free() @@ -76,7 +88,8 @@ public BoundExpression GetTemp(BoundDagTemp dagTemp) { if (!_map.TryGetValue(dagTemp, out BoundExpression result)) { - LocalSymbol temp = _factory.SynthesizedLocal(dagTemp.Type, syntax: _node, kind: SynthesizedLocalKind.SwitchCasePatternMatching); + var kind = _isSwitchStatement ? SynthesizedLocalKind.SwitchCasePatternMatching : SynthesizedLocalKind.LoweringTemp; + LocalSymbol temp = _factory.SynthesizedLocal(dagTemp.Type, syntax: _node, kind: kind); result = _factory.Local(temp); _map.Add(dagTemp, result); _temps.Add(temp); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_SwitchExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_SwitchExpression.cs index d59991b47fa1e..aed4004c80fc5 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_SwitchExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_SwitchExpression.cs @@ -20,14 +20,16 @@ public override BoundNode VisitConvertedSwitchExpression(BoundConvertedSwitchExp return SwitchExpressionLocalRewriter.Rewrite(this, node); } - private class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter + private sealed class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter { private SwitchExpressionLocalRewriter(BoundConvertedSwitchExpression node, LocalRewriter localRewriter) - : base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax), isSwitchStatement: false) + : base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax)) { } - public static BoundExpression Rewrite(LocalRewriter localRewriter, BoundConvertedSwitchExpression node) + protected override bool IsSwitchStatement => false; + + public static BoundExpression Rewrite(LocalRewriter localRewriter, BoundSwitchExpression node) { var rewriter = new SwitchExpressionLocalRewriter(node, localRewriter); BoundExpression result = rewriter.LowerSwitchExpression(node); @@ -55,7 +57,7 @@ private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression nod // decision tree, so the code in result is unreachable at this point. // Lower each switch expression arm - LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.SwitchCasePatternMatching); + LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression"); foreach (BoundSwitchExpressionArm arm in node.SwitchArms) { diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index d7b7a9d014fc2..209c8eed4aea1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -543,7 +543,6 @@ public override BoundNode VisitAwaitExpression(BoundAwaitExpression node) public override BoundNode VisitSpillSequence(BoundSpillSequence node) { - Debug.Assert(node.Locals.All(l => l.SynthesizedKind.IsLongLived())); var builder = new BoundSpillSequenceBuilder(); // Ensure later errors (e.g. in async rewriting) are associated with the correct node. diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index fc0a3366fad44..f27814b218202 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -471,7 +471,7 @@ private BoundExpression HoistRefInitialization(SynthesizedLocal local, BoundAssi if (F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug) { awaitSyntaxOpt = (AwaitExpressionSyntax)local.GetDeclaratorSyntax(); - syntaxOffset = this.OriginalMethod.CalculateLocalSyntaxOffset(awaitSyntaxOpt.SpanStart, awaitSyntaxOpt.SyntaxTree); + syntaxOffset = OriginalMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(awaitSyntaxOpt), awaitSyntaxOpt.SyntaxTree); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 37ec9d6f2ed50..bd971f62975c7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -175,7 +175,7 @@ private void CreateNonReusableLocalProxies( // EnC: When emitting the baseline (gen 0) the id is stored in a custom debug information attached to the kickoff method. // When emitting a delta the id is only used to map to the existing field in the previous generation. SyntaxNode declaratorSyntax = local.GetDeclaratorSyntax(); - int syntaxOffset = this.method.CalculateLocalSyntaxOffset(declaratorSyntax.SpanStart, declaratorSyntax.SyntaxTree); + int syntaxOffset = method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(declaratorSyntax), declaratorSyntax.SyntaxTree); int ordinal = synthesizedLocalOrdinals.AssignLocalOrdinal(synthesizedKind, syntaxOffset); id = new LocalDebugId(syntaxOffset, ordinal); diff --git a/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs b/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs index 440b91ae37496..864950d09fee3 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LambdaUtilities.cs @@ -460,6 +460,16 @@ internal static bool IsClosureScope(SyntaxNode node) return false; } + /// + /// Given a node that represents a variable declaration, lambda or a closure scope return the position to be used to calculate + /// the node's syntax offset with respect to its containing member. + /// + internal static int GetDeclaratorPosition(SyntaxNode node) + { + // To differentiate between nested switch expressions that start at the same offset, use the offset of the `switch` keyword. + return (node is SwitchExpressionSyntax switchExpression) ? switchExpression.SwitchKeyword.SpanStart : node.SpanStart; + } + private static SyntaxNode GetLocalFunctionBody(LocalFunctionStatementSyntax localFunctionStatementSyntax) { return (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody?.Expression; diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index ee386035a242d..82ef81c53c1d4 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -7288,31 +7288,1624 @@ static void M() #region Patterns [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] - public void SyntaxOffset_Pattern() + public void SyntaxOffset_IsPattern() { var source = @"class C { bool F(object o) => o is int i && o is 3 && o is bool; }"; var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + c.VerifyPdb("C.F", @" - + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_SwitchStatement() + { + string source = @" +class C +{ + public void Deconstruct() { } + public void Deconstruct(out int x) { x = 1; } + public void Deconstruct(out int x, out object y) { x = 2; y = new C(); } +} + +class D +{ + public int P { get; set; } + public D Q { get; set; } + public C R { get; set; } +} + +class Program +{ + static object F() => new C(); + static bool B() => true; + static int G(int x) => x; + + static int Main() + { + switch (F()) + { + // declaration pattern + case int x when G(x) > 10: return 1; + + // discard pattern + case bool _: return 2; + + // var pattern + case var (y, z): return 3; + + // constant pattern + case 4.0: return 4; + + // positional patterns + case C() when B(): return 5; + case (): return 6; + case C(int p, C(int q)): return 7; + case C(x: int p): return 8; + + // property pattern + case D { P: 1, Q: D { P: 2 }, R: C(int z) }: return 9; + + default: return 10; + }; + } +} +"; + var c = CreateCompilation(source, options: TestOptions.DebugDll, targetFramework: TargetFramework.NetCoreApp30); + var verifier = CompileAndVerify(c, verify: Verification.Skipped); + + // TODO: https://github.com/dotnet/roslyn/issues/37172 + // Synthesized temp variables used for patterns have the same type, syntax offset and kind. + // This breaks Enc mapping. + + verifier.VerifyIL("Program.Main", sequencePoints: "Program.Main", expectedIL: @" +{ + // Code size 453 (0x1c5) + .maxstack 3 + .locals init (int V_0, //x + object V_1, //y + object V_2, //z + int V_3, //p + int V_4, //q + int V_5, //p + int V_6, //z + object V_7, + System.Runtime.CompilerServices.ITuple V_8, + int V_9, + D V_10, + double V_11, + C V_12, + object V_13, + C V_14, + int V_15, + D V_16, + int V_17, + C V_18, + object V_19, + int V_20) + // sequence point: { + IL_0000: nop + // sequence point: switch (F()) + IL_0001: call ""object Program.F()"" + IL_0006: stloc.s V_19 + // sequence point: + IL_0008: ldloc.s V_19 + IL_000a: stloc.s V_7 + // sequence point: + IL_000c: ldloc.s V_7 + IL_000e: isinst ""int"" + IL_0013: brfalse.s IL_0022 + IL_0015: ldloc.s V_7 + IL_0017: unbox.any ""int"" + IL_001c: stloc.0 + // sequence point: + IL_001d: br IL_0162 + IL_0022: ldloc.s V_7 + IL_0024: isinst ""bool"" + IL_0029: brtrue IL_0173 + IL_002e: ldloc.s V_7 + IL_0030: isinst ""System.Runtime.CompilerServices.ITuple"" + IL_0035: stloc.s V_8 + IL_0037: ldloc.s V_8 + IL_0039: brfalse.s IL_0093 + IL_003b: ldloc.s V_8 + IL_003d: callvirt ""int System.Runtime.CompilerServices.ITuple.Length.get"" + IL_0042: stloc.s V_9 + // sequence point: + IL_0044: ldloc.s V_9 + IL_0046: ldc.i4.2 + IL_0047: bne.un.s IL_0060 + IL_0049: ldloc.s V_8 + IL_004b: ldc.i4.0 + IL_004c: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_0051: stloc.1 + // sequence point: + IL_0052: ldloc.s V_8 + IL_0054: ldc.i4.1 + IL_0055: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_005a: stloc.2 + // sequence point: + IL_005b: br IL_0178 + IL_0060: ldloc.s V_7 + IL_0062: isinst ""C"" + IL_0067: brtrue IL_0184 + IL_006c: br.s IL_0077 + IL_006e: ldloc.s V_9 + IL_0070: brfalse IL_01a1 + IL_0075: br.s IL_00c8 + IL_0077: ldloc.s V_9 + IL_0079: brfalse IL_01a1 + IL_007e: ldloc.s V_7 + IL_0080: isinst ""D"" + IL_0085: stloc.s V_10 + IL_0087: ldloc.s V_10 + IL_0089: brtrue IL_011a + IL_008e: br IL_01bc + IL_0093: ldloc.s V_7 + IL_0095: isinst ""double"" + IL_009a: brfalse.s IL_00ba + IL_009c: ldloc.s V_7 + IL_009e: unbox.any ""double"" + IL_00a3: stloc.s V_11 + // sequence point: + IL_00a5: ldloc.s V_11 + IL_00a7: ldc.r8 4 + IL_00b0: beq IL_017f + IL_00b5: br IL_01bc + IL_00ba: ldloc.s V_7 + IL_00bc: isinst ""C"" + IL_00c1: brtrue IL_0190 + IL_00c6: br.s IL_0108 + IL_00c8: ldloc.s V_7 + IL_00ca: castclass ""C"" + IL_00cf: stloc.s V_12 + // sequence point: + IL_00d1: ldloc.s V_12 + IL_00d3: ldloca.s V_3 + IL_00d5: ldloca.s V_13 + IL_00d7: callvirt ""void C.Deconstruct(out int, out object)"" + IL_00dc: nop + // sequence point: + IL_00dd: ldloc.s V_13 + IL_00df: isinst ""C"" + IL_00e4: stloc.s V_14 + IL_00e6: ldloc.s V_14 + IL_00e8: brfalse.s IL_00f9 + IL_00ea: ldloc.s V_14 + IL_00ec: ldloca.s V_4 + IL_00ee: callvirt ""void C.Deconstruct(out int)"" + IL_00f3: nop + // sequence point: + IL_00f4: br IL_01a6 + IL_00f9: ldloc.s V_12 + IL_00fb: ldloca.s V_5 + IL_00fd: callvirt ""void C.Deconstruct(out int)"" + IL_0102: nop + // sequence point: + IL_0103: br IL_01ad + IL_0108: ldloc.s V_7 + IL_010a: isinst ""D"" + IL_010f: stloc.s V_10 + IL_0111: ldloc.s V_10 + IL_0113: brtrue.s IL_011a + IL_0115: br IL_01bc + // sequence point: + IL_011a: ldloc.s V_10 + IL_011c: callvirt ""int D.P.get"" + IL_0121: stloc.s V_15 + // sequence point: + IL_0123: ldloc.s V_15 + IL_0125: ldc.i4.1 + IL_0126: bne.un IL_01bc + IL_012b: ldloc.s V_10 + IL_012d: callvirt ""D D.Q.get"" + IL_0132: stloc.s V_16 + // sequence point: + IL_0134: ldloc.s V_16 + IL_0136: brfalse IL_01bc + IL_013b: ldloc.s V_16 + IL_013d: callvirt ""int D.P.get"" + IL_0142: stloc.s V_17 + // sequence point: + IL_0144: ldloc.s V_17 + IL_0146: ldc.i4.2 + IL_0147: bne.un.s IL_01bc + IL_0149: ldloc.s V_10 + IL_014b: callvirt ""C D.R.get"" + IL_0150: stloc.s V_18 + // sequence point: + IL_0152: ldloc.s V_18 + IL_0154: brfalse.s IL_01bc + IL_0156: ldloc.s V_18 + IL_0158: ldloca.s V_6 + IL_015a: callvirt ""void C.Deconstruct(out int)"" + IL_015f: nop + // sequence point: + IL_0160: br.s IL_01b4 + // sequence point: when G(x) > 10 + IL_0162: ldloc.0 + IL_0163: call ""int Program.G(int)"" + IL_0168: ldc.i4.s 10 + IL_016a: bgt.s IL_016e + // sequence point: + IL_016c: br.s IL_01bc + // sequence point: return 1; + IL_016e: ldc.i4.1 + IL_016f: stloc.s V_20 + IL_0171: br.s IL_01c2 + // sequence point: return 2; + IL_0173: ldc.i4.2 + IL_0174: stloc.s V_20 + IL_0176: br.s IL_01c2 + // sequence point: + IL_0178: br.s IL_017a + // sequence point: return 3; + IL_017a: ldc.i4.3 + IL_017b: stloc.s V_20 + IL_017d: br.s IL_01c2 + // sequence point: return 4; + IL_017f: ldc.i4.4 + IL_0180: stloc.s V_20 + IL_0182: br.s IL_01c2 + // sequence point: when B() + IL_0184: call ""bool Program.B()"" + IL_0189: brtrue.s IL_019c + // sequence point: + IL_018b: br IL_006e + // sequence point: when B() + IL_0190: call ""bool Program.B()"" + IL_0195: brtrue.s IL_019c + // sequence point: + IL_0197: br IL_00c8 + // sequence point: return 5; + IL_019c: ldc.i4.5 + IL_019d: stloc.s V_20 + IL_019f: br.s IL_01c2 + // sequence point: return 6; + IL_01a1: ldc.i4.6 + IL_01a2: stloc.s V_20 + IL_01a4: br.s IL_01c2 + // sequence point: + IL_01a6: br.s IL_01a8 + // sequence point: return 7; + IL_01a8: ldc.i4.7 + IL_01a9: stloc.s V_20 + IL_01ab: br.s IL_01c2 + // sequence point: + IL_01ad: br.s IL_01af + // sequence point: return 8; + IL_01af: ldc.i4.8 + IL_01b0: stloc.s V_20 + IL_01b2: br.s IL_01c2 + // sequence point: + IL_01b4: br.s IL_01b6 + // sequence point: return 9; + IL_01b6: ldc.i4.s 9 + IL_01b8: stloc.s V_20 + IL_01ba: br.s IL_01c2 + // sequence point: return 10; + IL_01bc: ldc.i4.s 10 + IL_01be: stloc.s V_20 + IL_01c0: br.s IL_01c2 + // sequence point: } + IL_01c2: ldloc.s V_20 + IL_01c4: ret +} +", source: source); + + verifier.VerifyPdb("Program.Main", @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_SwitchExpression() + { + string source = @" +class C +{ + public void Deconstruct() { } + public void Deconstruct(out int x) { x = 1; } + public void Deconstruct(out int x, out object y) { x = 2; y = new C(); } +} + +class D +{ + public int P { get; set; } + public D Q { get; set; } + public C R { get; set; } +} + +class Program +{ + static object F() => new C(); + static bool B() => true; + static int G(int x) => x; + + static void Main() + { + var a = F() switch + { + // declaration pattern + int x when G(x) > 10 => 1, + + // discard pattern + bool _ => 2, + + // var pattern + var (y, z) => 3, + + // constant pattern + 4.0 => 4, + + // positional patterns + C() when B() => 5, + () => 6, + C(int p, C(int q)) => 7, + C(x: int p) => 8, + + // property pattern + D { P: 1, Q: D { P: 2 }, R: C (int z) } => 9, + + _ => 10, + }; + } +} +"; + var c = CreateCompilation(source, options: TestOptions.DebugDll, targetFramework: TargetFramework.NetCoreApp30); + var verifier = CompileAndVerify(c, verify: Verification.Skipped); + + // note not sequence points emitted within the switch expression + + verifier.VerifyIL("Program.Main", sequencePoints: "Program.Main", expectedIL: @" +{ + // Code size 454 (0x1c6) + .maxstack 3 + .locals init (int V_0, //a + int V_1, //x + object V_2, //y + object V_3, //z + int V_4, //p + int V_5, //q + int V_6, //p + int V_7, //z + int V_8, + object V_9, + System.Runtime.CompilerServices.ITuple V_10, + int V_11, + D V_12, + double V_13, + C V_14, + object V_15, + C V_16, + int V_17, + D V_18, + int V_19, + C V_20, + int V_21) + -IL_0000: nop + -IL_0001: call ""object Program.F()"" + IL_0006: stloc.s V_9 + IL_0008: ldloc.s V_9 + IL_000a: isinst ""int"" + IL_000f: brfalse.s IL_001e + IL_0011: ldloc.s V_9 + IL_0013: unbox.any ""int"" + IL_0018: stloc.1 + IL_0019: br IL_015e + IL_001e: ldloc.s V_9 + IL_0020: isinst ""bool"" + IL_0025: brtrue IL_016f + IL_002a: ldloc.s V_9 + IL_002c: isinst ""System.Runtime.CompilerServices.ITuple"" + IL_0031: stloc.s V_10 + IL_0033: ldloc.s V_10 + IL_0035: brfalse.s IL_008f + IL_0037: ldloc.s V_10 + IL_0039: callvirt ""int System.Runtime.CompilerServices.ITuple.Length.get"" + IL_003e: stloc.s V_11 + IL_0040: ldloc.s V_11 + IL_0042: ldc.i4.2 + IL_0043: bne.un.s IL_005c + IL_0045: ldloc.s V_10 + IL_0047: ldc.i4.0 + IL_0048: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_004d: stloc.2 + IL_004e: ldloc.s V_10 + IL_0050: ldc.i4.1 + IL_0051: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_0056: stloc.3 + IL_0057: br IL_0174 + IL_005c: ldloc.s V_9 + IL_005e: isinst ""C"" + IL_0063: brtrue IL_0180 + IL_0068: br.s IL_0073 + IL_006a: ldloc.s V_11 + IL_006c: brfalse IL_019d + IL_0071: br.s IL_00c4 + IL_0073: ldloc.s V_11 + IL_0075: brfalse IL_019d + IL_007a: ldloc.s V_9 + IL_007c: isinst ""D"" + IL_0081: stloc.s V_12 + IL_0083: ldloc.s V_12 + IL_0085: brtrue IL_0116 + IL_008a: br IL_01b8 + IL_008f: ldloc.s V_9 + IL_0091: isinst ""double"" + IL_0096: brfalse.s IL_00b6 + IL_0098: ldloc.s V_9 + IL_009a: unbox.any ""double"" + IL_009f: stloc.s V_13 + IL_00a1: ldloc.s V_13 + IL_00a3: ldc.r8 4 + IL_00ac: beq IL_017b + IL_00b1: br IL_01b8 + IL_00b6: ldloc.s V_9 + IL_00b8: isinst ""C"" + IL_00bd: brtrue IL_018c + IL_00c2: br.s IL_0104 + IL_00c4: ldloc.s V_9 + IL_00c6: castclass ""C"" + IL_00cb: stloc.s V_14 + IL_00cd: ldloc.s V_14 + IL_00cf: ldloca.s V_4 + IL_00d1: ldloca.s V_15 + IL_00d3: callvirt ""void C.Deconstruct(out int, out object)"" + IL_00d8: nop + IL_00d9: ldloc.s V_15 + IL_00db: isinst ""C"" + IL_00e0: stloc.s V_16 + IL_00e2: ldloc.s V_16 + IL_00e4: brfalse.s IL_00f5 + IL_00e6: ldloc.s V_16 + IL_00e8: ldloca.s V_5 + IL_00ea: callvirt ""void C.Deconstruct(out int)"" + IL_00ef: nop + IL_00f0: br IL_01a2 + IL_00f5: ldloc.s V_14 + IL_00f7: ldloca.s V_6 + IL_00f9: callvirt ""void C.Deconstruct(out int)"" + IL_00fe: nop + IL_00ff: br IL_01a9 + IL_0104: ldloc.s V_9 + IL_0106: isinst ""D"" + IL_010b: stloc.s V_12 + IL_010d: ldloc.s V_12 + IL_010f: brtrue.s IL_0116 + IL_0111: br IL_01b8 + IL_0116: ldloc.s V_12 + IL_0118: callvirt ""int D.P.get"" + IL_011d: stloc.s V_17 + IL_011f: ldloc.s V_17 + IL_0121: ldc.i4.1 + IL_0122: bne.un IL_01b8 + IL_0127: ldloc.s V_12 + IL_0129: callvirt ""D D.Q.get"" + IL_012e: stloc.s V_18 + IL_0130: ldloc.s V_18 + IL_0132: brfalse IL_01b8 + IL_0137: ldloc.s V_18 + IL_0139: callvirt ""int D.P.get"" + IL_013e: stloc.s V_19 + IL_0140: ldloc.s V_19 + IL_0142: ldc.i4.2 + IL_0143: bne.un.s IL_01b8 + IL_0145: ldloc.s V_12 + IL_0147: callvirt ""C D.R.get"" + IL_014c: stloc.s V_20 + IL_014e: ldloc.s V_20 + IL_0150: brfalse.s IL_01b8 + IL_0152: ldloc.s V_20 + IL_0154: ldloca.s V_7 + IL_0156: callvirt ""void C.Deconstruct(out int)"" + IL_015b: nop + IL_015c: br.s IL_01b0 + IL_015e: ldloc.1 + IL_015f: call ""int Program.G(int)"" + IL_0164: ldc.i4.s 10 + IL_0166: bgt.s IL_016a + IL_0168: br.s IL_01b8 + IL_016a: ldc.i4.1 + IL_016b: stloc.s V_8 + IL_016d: br.s IL_01be + IL_016f: ldc.i4.2 + IL_0170: stloc.s V_8 + IL_0172: br.s IL_01be + IL_0174: br.s IL_0176 + IL_0176: ldc.i4.3 + IL_0177: stloc.s V_8 + IL_0179: br.s IL_01be + IL_017b: ldc.i4.4 + IL_017c: stloc.s V_8 + IL_017e: br.s IL_01be + IL_0180: call ""bool Program.B()"" + IL_0185: brtrue.s IL_0198 + IL_0187: br IL_006a + IL_018c: call ""bool Program.B()"" + IL_0191: brtrue.s IL_0198 + IL_0193: br IL_00c4 + IL_0198: ldc.i4.5 + IL_0199: stloc.s V_8 + IL_019b: br.s IL_01be + IL_019d: ldc.i4.6 + IL_019e: stloc.s V_8 + IL_01a0: br.s IL_01be + IL_01a2: br.s IL_01a4 + IL_01a4: ldc.i4.7 + IL_01a5: stloc.s V_8 + IL_01a7: br.s IL_01be + IL_01a9: br.s IL_01ab + IL_01ab: ldc.i4.8 + IL_01ac: stloc.s V_8 + IL_01ae: br.s IL_01be + IL_01b0: br.s IL_01b2 + IL_01b2: ldc.i4.s 9 + IL_01b4: stloc.s V_8 + IL_01b6: br.s IL_01be + IL_01b8: ldc.i4.s 10 + IL_01ba: stloc.s V_8 + IL_01bc: br.s IL_01be + IL_01be: ldloc.s V_8 + IL_01c0: stloc.s V_21 + IL_01c2: ldloc.s V_21 + IL_01c4: stloc.0 + -IL_01c5: ret +} +"); + + verifier.VerifyPdb("Program.Main", @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_IsPattern() + { + string source = @" +class C +{ + public void Deconstruct() { } + public void Deconstruct(out int x) { x = 1; } + public void Deconstruct(out int x, out object y) { x = 2; y = new C(); } +} + +class D +{ + public int P { get; set; } + public D Q { get; set; } + public C R { get; set; } +} + +class Program +{ + static object F() => new C(); + static bool B() => true; + static int G(int x) => x; + + static bool M() + { + object obj = F(); + return + // declaration pattern + obj is int x || + + // discard pattern + obj is bool _ || + + // var pattern + obj is var (y, z1) || + + // constant pattern + obj is 4.0 || + + // positional patterns + obj is C() || + obj is () || + obj is C(int p1, C(int q)) || + obj is C(x: int p2) || + + // property pattern + obj is D { P: 1, Q: D { P: 2 }, R: C(int z2) }; + } +} +"; + var c = CreateCompilation(source, options: TestOptions.DebugDll, targetFramework: TargetFramework.NetCoreApp30); + var verifier = CompileAndVerify(c, verify: Verification.Skipped); + + verifier.VerifyIL("Program.M", sequencePoints: "Program.M", expectedIL: @" +{ + // Code size 301 (0x12d) + .maxstack 3 + .locals init (object V_0, //obj + int V_1, //x + object V_2, //y + object V_3, //z1 + int V_4, //p1 + int V_5, //q + int V_6, //p2 + int V_7, //z2 + System.Runtime.CompilerServices.ITuple V_8, + C V_9, + object V_10, + C V_11, + D V_12, + D V_13, + bool V_14) + -IL_0000: nop + -IL_0001: call ""object Program.F()"" + IL_0006: stloc.0 + -IL_0007: ldloc.0 + IL_0008: isinst ""int"" + IL_000d: brfalse.s IL_001b + IL_000f: ldloc.0 + IL_0010: unbox.any ""int"" + IL_0015: stloc.1 + IL_0016: br IL_0125 + IL_001b: ldloc.0 + IL_001c: isinst ""bool"" + IL_0021: brtrue IL_0125 + IL_0026: ldloc.0 + IL_0027: isinst ""System.Runtime.CompilerServices.ITuple"" + IL_002c: stloc.s V_8 + IL_002e: ldloc.s V_8 + IL_0030: brfalse.s IL_0053 + IL_0032: ldloc.s V_8 + IL_0034: callvirt ""int System.Runtime.CompilerServices.ITuple.Length.get"" + IL_0039: ldc.i4.2 + IL_003a: bne.un.s IL_0053 + IL_003c: ldloc.s V_8 + IL_003e: ldc.i4.0 + IL_003f: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_0044: stloc.2 + IL_0045: ldloc.s V_8 + IL_0047: ldc.i4.1 + IL_0048: callvirt ""object System.Runtime.CompilerServices.ITuple.this[int].get"" + IL_004d: stloc.3 + IL_004e: br IL_0125 + IL_0053: ldloc.0 + IL_0054: isinst ""double"" + IL_0059: brfalse.s IL_006f + IL_005b: ldloc.0 + IL_005c: unbox.any ""double"" + IL_0061: ldc.r8 4 + IL_006a: beq IL_0125 + IL_006f: ldloc.0 + IL_0070: isinst ""C"" + IL_0075: brtrue IL_0125 + IL_007a: ldloc.0 + IL_007b: isinst ""System.Runtime.CompilerServices.ITuple"" + IL_0080: stloc.s V_8 + IL_0082: ldloc.s V_8 + IL_0084: brfalse.s IL_0092 + IL_0086: ldloc.s V_8 + IL_0088: callvirt ""int System.Runtime.CompilerServices.ITuple.Length.get"" + IL_008d: brfalse IL_0125 + IL_0092: ldloc.0 + IL_0093: isinst ""C"" + IL_0098: stloc.s V_9 + IL_009a: ldloc.s V_9 + IL_009c: brfalse.s IL_00c3 + IL_009e: ldloc.s V_9 + IL_00a0: ldloca.s V_4 + IL_00a2: ldloca.s V_10 + IL_00a4: callvirt ""void C.Deconstruct(out int, out object)"" + IL_00a9: nop + IL_00aa: ldloc.s V_10 + IL_00ac: isinst ""C"" + IL_00b1: stloc.s V_11 + IL_00b3: ldloc.s V_11 + IL_00b5: brfalse.s IL_00c3 + IL_00b7: ldloc.s V_11 + IL_00b9: ldloca.s V_5 + IL_00bb: callvirt ""void C.Deconstruct(out int)"" + IL_00c0: nop + IL_00c1: br.s IL_0125 + IL_00c3: ldloc.0 + IL_00c4: isinst ""C"" + IL_00c9: stloc.s V_11 + IL_00cb: ldloc.s V_11 + IL_00cd: brfalse.s IL_00db + IL_00cf: ldloc.s V_11 + IL_00d1: ldloca.s V_6 + IL_00d3: callvirt ""void C.Deconstruct(out int)"" + IL_00d8: nop + IL_00d9: br.s IL_0125 + IL_00db: ldloc.0 + IL_00dc: isinst ""D"" + IL_00e1: stloc.s V_12 + IL_00e3: ldloc.s V_12 + IL_00e5: brfalse.s IL_0122 + IL_00e7: ldloc.s V_12 + IL_00e9: callvirt ""int D.P.get"" + IL_00ee: ldc.i4.1 + IL_00ef: bne.un.s IL_0122 + IL_00f1: ldloc.s V_12 + IL_00f3: callvirt ""D D.Q.get"" + IL_00f8: stloc.s V_13 + IL_00fa: ldloc.s V_13 + IL_00fc: brfalse.s IL_0122 + IL_00fe: ldloc.s V_13 + IL_0100: callvirt ""int D.P.get"" + IL_0105: ldc.i4.2 + IL_0106: bne.un.s IL_0122 + IL_0108: ldloc.s V_12 + IL_010a: callvirt ""C D.R.get"" + IL_010f: stloc.s V_11 + IL_0111: ldloc.s V_11 + IL_0113: brfalse.s IL_0122 + IL_0115: ldloc.s V_11 + IL_0117: ldloca.s V_7 + IL_0119: callvirt ""void C.Deconstruct(out int)"" + IL_011e: nop + IL_011f: ldc.i4.1 + IL_0120: br.s IL_0123 + IL_0122: ldc.i4.0 + IL_0123: br.s IL_0126 + IL_0125: ldc.i4.1 + IL_0126: stloc.s V_14 + IL_0128: br.s IL_012a + -IL_012a: ldloc.s V_14 + IL_012c: ret +} +"); + + verifier.VerifyPdb("Program.M", @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(37232, "https://github.com/dotnet/roslyn/issues/37232")] + [WorkItem(37237, "https://github.com/dotnet/roslyn/issues/37237")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_SwitchExpression_Closures() + { + string source = @" +using System; +public class C +{ + static int M() + { + return F() switch + { + 1 => F() switch + { + C { P: int p, Q: C { P: int q } } => G(() => p + q), + _ => 10 + }, + 2 => F() switch + { + C { P: int r } => G(() => r), + _ => 20 + }, + C { Q: int s } => G(() => s), + _ => 0 + } + switch + { + var t when t > 0 => G(() => t), + _ => 0 + }; + } + + object P { get; set; } + object Q { get; set; } + static object F() => null; + static int G(Func f) => 0; +} +"; + var c = CreateCompilation(source, options: TestOptions.DebugDll); + var verifier = CompileAndVerify(c); + + // TODO: https://github.com/dotnet/roslyn/issues/37237 + // There should be no sequence points emitted within the switch expressions. + // + // TODO: https://github.com/dotnet/roslyn/issues/37232 + // The values of the closure offsets are incorrect. + + verifier.VerifyIL("C.M", sequencePoints: "C.M", expectedIL: @" +{ + // Code size 459 (0x1cb) + .maxstack 2 + .locals init (C.<>c__DisplayClass0_0 V_0, //CS$<>8__locals0 + int V_1, + int V_2, + C.<>c__DisplayClass0_1 V_3, //CS$<>8__locals1 + int V_4, + object V_5, + int V_6, + C V_7, + object V_8, + int V_9, + C.<>c__DisplayClass0_2 V_10, //CS$<>8__locals2 + int V_11, + object V_12, + C V_13, + object V_14, + object V_15, + C V_16, + object V_17, + int V_18, + C.<>c__DisplayClass0_3 V_19, //CS$<>8__locals3 + int V_20, + int V_21) + -IL_0000: nop + ~IL_0001: newobj ""C.<>c__DisplayClass0_0..ctor()"" + IL_0006: stloc.0 + ~IL_0007: newobj ""C.<>c__DisplayClass0_1..ctor()"" + IL_000c: stloc.3 + IL_000d: call ""object C.F()"" + IL_0012: stloc.s V_5 + IL_0014: ldloc.s V_5 + IL_0016: isinst ""int"" + IL_001b: brfalse.s IL_003a + IL_001d: ldloc.s V_5 + IL_001f: unbox.any ""int"" + IL_0024: stloc.s V_6 + IL_0026: ldloc.s V_6 + IL_0028: ldc.i4.1 + IL_0029: beq.s IL_0071 + IL_002b: br.s IL_002d + IL_002d: ldloc.s V_6 + IL_002f: ldc.i4.2 + IL_0030: beq IL_010f + IL_0035: br IL_018b + IL_003a: ldloc.s V_5 + IL_003c: isinst ""C"" + IL_0041: stloc.s V_7 + IL_0043: ldloc.s V_7 + IL_0045: brfalse IL_018b + IL_004a: ldloc.s V_7 + IL_004c: callvirt ""object C.Q.get"" + IL_0051: stloc.s V_8 + IL_0053: ldloc.s V_8 + IL_0055: isinst ""int"" + IL_005a: brfalse IL_018b + IL_005f: ldloc.3 + IL_0060: ldloc.s V_8 + IL_0062: unbox.any ""int"" + IL_0067: stfld ""int C.<>c__DisplayClass0_1.5__3"" + IL_006c: br IL_0174 + ~IL_0071: newobj ""C.<>c__DisplayClass0_2..ctor()"" + IL_0076: stloc.s V_10 + IL_0078: call ""object C.F()"" + IL_007d: stloc.s V_12 + IL_007f: ldloc.s V_12 + IL_0081: isinst ""C"" + IL_0086: stloc.s V_13 + IL_0088: ldloc.s V_13 + IL_008a: brfalse.s IL_00fc + IL_008c: ldloc.s V_13 + IL_008e: callvirt ""object C.P.get"" + IL_0093: stloc.s V_14 + IL_0095: ldloc.s V_14 + IL_0097: isinst ""int"" + IL_009c: brfalse.s IL_00fc + IL_009e: ldloc.s V_10 + IL_00a0: ldloc.s V_14 + IL_00a2: unbox.any ""int"" + IL_00a7: stfld ""int C.<>c__DisplayClass0_2.

5__4"" + IL_00ac: ldloc.s V_13 + IL_00ae: callvirt ""object C.Q.get"" + IL_00b3: stloc.s V_15 + IL_00b5: ldloc.s V_15 + IL_00b7: isinst ""C"" + IL_00bc: stloc.s V_16 + IL_00be: ldloc.s V_16 + IL_00c0: brfalse.s IL_00fc + IL_00c2: ldloc.s V_16 + IL_00c4: callvirt ""object C.P.get"" + IL_00c9: stloc.s V_17 + IL_00cb: ldloc.s V_17 + IL_00cd: isinst ""int"" + IL_00d2: brfalse.s IL_00fc + IL_00d4: ldloc.s V_10 + IL_00d6: ldloc.s V_17 + IL_00d8: unbox.any ""int"" + IL_00dd: stfld ""int C.<>c__DisplayClass0_2.5__5"" + IL_00e2: br.s IL_00e4 + IL_00e4: br.s IL_00e6 + IL_00e6: ldloc.s V_10 + IL_00e8: ldftn ""int C.<>c__DisplayClass0_2.b__2()"" + IL_00ee: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_00f3: call ""int C.G(System.Func)"" + IL_00f8: stloc.s V_11 + IL_00fa: br.s IL_0102 + IL_00fc: ldc.i4.s 10 + IL_00fe: stloc.s V_11 + IL_0100: br.s IL_0102 + IL_0102: ldloc.s V_11 + IL_0104: stloc.s V_18 + IL_0106: ldloc.s V_18 + IL_0108: stloc.s V_4 + IL_010a: br IL_0190 + ~IL_010f: newobj ""C.<>c__DisplayClass0_3..ctor()"" + IL_0114: stloc.s V_19 + IL_0116: call ""object C.F()"" + IL_011b: stloc.s V_17 + IL_011d: ldloc.s V_17 + IL_011f: isinst ""C"" + IL_0124: stloc.s V_16 + IL_0126: ldloc.s V_16 + IL_0128: brfalse.s IL_0164 + IL_012a: ldloc.s V_16 + IL_012c: callvirt ""object C.P.get"" + IL_0131: stloc.s V_15 + IL_0133: ldloc.s V_15 + IL_0135: isinst ""int"" + IL_013a: brfalse.s IL_0164 + IL_013c: ldloc.s V_19 + IL_013e: ldloc.s V_15 + IL_0140: unbox.any ""int"" + IL_0145: stfld ""int C.<>c__DisplayClass0_3.5__6"" + IL_014a: br.s IL_014c + IL_014c: br.s IL_014e + IL_014e: ldloc.s V_19 + IL_0150: ldftn ""int C.<>c__DisplayClass0_3.b__3()"" + IL_0156: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_015b: call ""int C.G(System.Func)"" + IL_0160: stloc.s V_11 + IL_0162: br.s IL_016a + IL_0164: ldc.i4.s 20 + IL_0166: stloc.s V_11 + IL_0168: br.s IL_016a + IL_016a: ldloc.s V_11 + IL_016c: stloc.s V_20 + IL_016e: ldloc.s V_20 + IL_0170: stloc.s V_4 + IL_0172: br.s IL_0190 + IL_0174: br.s IL_0176 + IL_0176: ldloc.3 + IL_0177: ldftn ""int C.<>c__DisplayClass0_1.b__1()"" + IL_017d: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_0182: call ""int C.G(System.Func)"" + IL_0187: stloc.s V_4 + IL_0189: br.s IL_0190 + IL_018b: ldc.i4.0 + IL_018c: stloc.s V_4 + IL_018e: br.s IL_0190 + IL_0190: ldloc.s V_4 + IL_0192: stloc.s V_9 + IL_0194: ldloc.0 + IL_0195: ldloc.s V_9 + IL_0197: stfld ""int C.<>c__DisplayClass0_0.5__2"" + IL_019c: br.s IL_019e + IL_019e: ldloc.0 + IL_019f: ldfld ""int C.<>c__DisplayClass0_0.5__2"" + IL_01a4: ldc.i4.0 + IL_01a5: bgt.s IL_01a9 + IL_01a7: br.s IL_01bd + IL_01a9: ldloc.0 + IL_01aa: ldftn ""int C.<>c__DisplayClass0_0.b__0()"" + IL_01b0: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_01b5: call ""int C.G(System.Func)"" + IL_01ba: stloc.1 + IL_01bb: br.s IL_01c1 + IL_01bd: ldc.i4.0 + IL_01be: stloc.1 + IL_01bf: br.s IL_01c1 + IL_01c1: ldloc.1 + IL_01c2: stloc.2 + IL_01c3: ldloc.2 + IL_01c4: stloc.s V_21 + IL_01c6: br.s IL_01c8 + -IL_01c8: ldloc.s V_21 + IL_01ca: ret +}"); + verifier.VerifyPdb("C.M", @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(12378, "https://github.com/dotnet/roslyn/issues/12378")] + [WorkItem(13971, "https://github.com/dotnet/roslyn/issues/13971")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_SwitchStatement_Constant() + { + string source = +@"class Program +{ + static void M(object o) + { + switch (o) + { + case 1 when o == null: + case 4: + case 2 when o == null: + break; + case 1 when o != null: + case 5: + case 3 when o != null: + break; + default: + break; + case 1: + break; + } + switch (o) + { + case 1: + break; + default: + break; + } + switch (o) + { + default: + break; + } + } +}"; + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + CompileAndVerify(c).VerifyIL(qualifiedMethodName: "Program.M", sequencePoints: "Program.M", source: source, +expectedIL: @"{ + // Code size 123 (0x7b) + .maxstack 2 + .locals init (object V_0, + int V_1, + object V_2, + object V_3, + int V_4, + object V_5, + object V_6, + object V_7) + // sequence point: { + IL_0000: nop + // sequence point: switch (o) + IL_0001: ldarg.0 + IL_0002: stloc.2 + // sequence point: + IL_0003: ldloc.2 + IL_0004: stloc.0 + // sequence point: + IL_0005: ldloc.0 + IL_0006: isinst ""int"" + IL_000b: brfalse.s IL_004a + IL_000d: ldloc.0 + IL_000e: unbox.any ""int"" + IL_0013: stloc.1 + // sequence point: + IL_0014: ldloc.1 + IL_0015: ldc.i4.1 + IL_0016: sub + IL_0017: switch ( + IL_0032, + IL_0037, + IL_0043, + IL_003c, + IL_0048) + IL_0030: br.s IL_004a + // sequence point: when o == null + IL_0032: ldarg.0 + IL_0033: brfalse.s IL_003c + // sequence point: + IL_0035: br.s IL_003e + // sequence point: when o == null + IL_0037: ldarg.0 + IL_0038: brfalse.s IL_003c + // sequence point: + IL_003a: br.s IL_004a + // sequence point: break; + IL_003c: br.s IL_004e + // sequence point: when o != null + IL_003e: ldarg.0 + IL_003f: brtrue.s IL_0048 + // sequence point: + IL_0041: br.s IL_004c + // sequence point: when o != null + IL_0043: ldarg.0 + IL_0044: brtrue.s IL_0048 + // sequence point: + IL_0046: br.s IL_004a + // sequence point: break; + IL_0048: br.s IL_004e + // sequence point: break; + IL_004a: br.s IL_004e + // sequence point: break; + IL_004c: br.s IL_004e + // sequence point: switch (o) + IL_004e: ldarg.0 + IL_004f: stloc.s V_5 + // sequence point: + IL_0051: ldloc.s V_5 + IL_0053: stloc.3 + // sequence point: + IL_0054: ldloc.3 + IL_0055: isinst ""int"" + IL_005a: brfalse.s IL_006d + IL_005c: ldloc.3 + IL_005d: unbox.any ""int"" + IL_0062: stloc.s V_4 + // sequence point: + IL_0064: ldloc.s V_4 + IL_0066: ldc.i4.1 + IL_0067: beq.s IL_006b + IL_0069: br.s IL_006d + // sequence point: break; + IL_006b: br.s IL_006f + // sequence point: break; + IL_006d: br.s IL_006f + // sequence point: switch (o) + IL_006f: ldarg.0 + IL_0070: stloc.s V_7 + // sequence point: + IL_0072: ldloc.s V_7 + IL_0074: stloc.s V_6 + // sequence point: + IL_0076: br.s IL_0078 + // sequence point: break; + IL_0078: br.s IL_007a + // sequence point: } + IL_007a: ret +}"); + c.VerifyPdb( +@" + + + + + + + + + + + + + + + + + + + + + + + + + + +"); + } + + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void Patterns_SwitchStatement_Tuple() + { + string source = +@"public class C +{ + static int F(int i) + { + switch (G()) + { + case (1, 2): return 3; + default: return 0; + }; + } + + static (object, object) G() => (2, 3); +}"; + var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, references: s_valueTupleRefs); + var cv = CompileAndVerify(c); + + cv.VerifyIL("C.F", @" +{ + // Code size 80 (0x50) + .maxstack 2 + .locals init (System.ValueTuple V_0, + object V_1, + int V_2, + object V_3, + int V_4, + System.ValueTuple V_5, + int V_6) + IL_0000: nop + IL_0001: call ""(object, object) C.G()"" + IL_0006: stloc.s V_5 + IL_0008: ldloc.s V_5 + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: ldfld ""object System.ValueTuple.Item1"" + IL_0011: stloc.1 + IL_0012: ldloc.1 + IL_0013: isinst ""int"" + IL_0018: brfalse.s IL_0048 + IL_001a: ldloc.1 + IL_001b: unbox.any ""int"" + IL_0020: stloc.2 + IL_0021: ldloc.2 + IL_0022: ldc.i4.1 + IL_0023: bne.un.s IL_0048 + IL_0025: ldloc.0 + IL_0026: ldfld ""object System.ValueTuple.Item2"" + IL_002b: stloc.3 + IL_002c: ldloc.3 + IL_002d: isinst ""int"" + IL_0032: brfalse.s IL_0048 + IL_0034: ldloc.3 + IL_0035: unbox.any ""int"" + IL_003a: stloc.s V_4 + IL_003c: ldloc.s V_4 + IL_003e: ldc.i4.2 + IL_003f: beq.s IL_0043 + IL_0041: br.s IL_0048 + IL_0043: ldc.i4.3 + IL_0044: stloc.s V_6 + IL_0046: br.s IL_004d + IL_0048: ldc.i4.0 + IL_0049: stloc.s V_6 + IL_004b: br.s IL_004d + IL_004d: ldloc.s V_6 + IL_004f: ret +} +"); + + c.VerifyPdb("C.F", @" + + + + + + - - + + + + + + + - + + + - - - "); @@ -7962,6 +9555,44 @@ static int F(System.Func x) var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); + var v = CompileAndVerify(c); + v.VerifyIL("C..ctor", sequencePoints: "C..ctor", expectedIL: @" +{ + // Code size 90 (0x5a) + .maxstack 4 + .locals init (C.<>c__DisplayClass4_0 V_0, //CS$<>8__locals0 + C.<>c__DisplayClass4_1 V_1) //CS$<>8__locals1 + ~IL_0000: newobj ""C.<>c__DisplayClass4_0..ctor()"" + IL_0005: stloc.0 + -IL_0006: ldarg.0 + IL_0007: ldloc.0 + IL_0008: ldflda ""int C.<>c__DisplayClass4_0.z"" + IL_000d: call ""int C.G(out int)"" + IL_0012: ldloc.0 + IL_0013: ldftn ""int C.<>c__DisplayClass4_0.<.ctor>b__0()"" + IL_0019: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_001e: call ""int C.F(System.Func)"" + IL_0023: add + IL_0024: stfld ""int C.y1"" + ~IL_0029: newobj ""C.<>c__DisplayClass4_1..ctor()"" + IL_002e: stloc.1 + -IL_002f: ldarg.0 + IL_0030: ldloc.1 + IL_0031: ldflda ""int C.<>c__DisplayClass4_1.u"" + IL_0036: call ""int C.G(out int)"" + IL_003b: ldloc.1 + IL_003c: ldftn ""int C.<>c__DisplayClass4_1.<.ctor>b__1()"" + IL_0042: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_0047: call ""int C.F(System.Func)"" + IL_004c: add + IL_004d: stfld ""int C.y2"" + IL_0052: ldarg.0 + IL_0053: call ""object..ctor()"" + IL_0058: nop + IL_0059: ret +} +"); + c.VerifyPdb("C..ctor", @" @@ -8338,11 +9969,11 @@ public void SyntaxOffset_OutVarInSwitchExpresison() - - - - - + + + + + @@ -8470,204 +10101,6 @@ partial class C "); } - [WorkItem(12378, "https://github.com/dotnet/roslyn/issues/12378")] - [WorkItem(13971, "https://github.com/dotnet/roslyn/issues/13971")] - [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] - public void PatternSwitchSequencePoints() - { - string source = -@"class Program -{ - static void M(object o) - { - switch (o) - { - case 1 when o == null: - case 4: - case 2 when o == null: - break; - case 1 when o != null: - case 5: - case 3 when o != null: - break; - default: - break; - case 1: - break; - } - switch (o) - { - case 1: - break; - default: - break; - } - switch (o) - { - default: - break; - } - } -}"; - var c = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll); - CompileAndVerify(c).VerifyIL(qualifiedMethodName: "Program.M", sequencePoints: "Program.M", source: source, -expectedIL: @"{ - // Code size 123 (0x7b) - .maxstack 2 - .locals init (object V_0, - int V_1, - object V_2, - object V_3, - int V_4, - object V_5, - object V_6, - object V_7) - // sequence point: { - IL_0000: nop - // sequence point: switch (o) - IL_0001: ldarg.0 - IL_0002: stloc.2 - // sequence point: - IL_0003: ldloc.2 - IL_0004: stloc.0 - // sequence point: - IL_0005: ldloc.0 - IL_0006: isinst ""int"" - IL_000b: brfalse.s IL_004a - IL_000d: ldloc.0 - IL_000e: unbox.any ""int"" - IL_0013: stloc.1 - // sequence point: - IL_0014: ldloc.1 - IL_0015: ldc.i4.1 - IL_0016: sub - IL_0017: switch ( - IL_0032, - IL_0037, - IL_0043, - IL_003c, - IL_0048) - IL_0030: br.s IL_004a - // sequence point: when o == null - IL_0032: ldarg.0 - IL_0033: brfalse.s IL_003c - // sequence point: - IL_0035: br.s IL_003e - // sequence point: when o == null - IL_0037: ldarg.0 - IL_0038: brfalse.s IL_003c - // sequence point: - IL_003a: br.s IL_004a - // sequence point: break; - IL_003c: br.s IL_004e - // sequence point: when o != null - IL_003e: ldarg.0 - IL_003f: brtrue.s IL_0048 - // sequence point: - IL_0041: br.s IL_004c - // sequence point: when o != null - IL_0043: ldarg.0 - IL_0044: brtrue.s IL_0048 - // sequence point: - IL_0046: br.s IL_004a - // sequence point: break; - IL_0048: br.s IL_004e - // sequence point: break; - IL_004a: br.s IL_004e - // sequence point: break; - IL_004c: br.s IL_004e - // sequence point: switch (o) - IL_004e: ldarg.0 - IL_004f: stloc.s V_5 - // sequence point: - IL_0051: ldloc.s V_5 - IL_0053: stloc.3 - // sequence point: - IL_0054: ldloc.3 - IL_0055: isinst ""int"" - IL_005a: brfalse.s IL_006d - IL_005c: ldloc.3 - IL_005d: unbox.any ""int"" - IL_0062: stloc.s V_4 - // sequence point: - IL_0064: ldloc.s V_4 - IL_0066: ldc.i4.1 - IL_0067: beq.s IL_006b - IL_0069: br.s IL_006d - // sequence point: break; - IL_006b: br.s IL_006f - // sequence point: break; - IL_006d: br.s IL_006f - // sequence point: switch (o) - IL_006f: ldarg.0 - IL_0070: stloc.s V_7 - // sequence point: - IL_0072: ldloc.s V_7 - IL_0074: stloc.s V_6 - // sequence point: - IL_0076: br.s IL_0078 - // sequence point: break; - IL_0078: br.s IL_007a - // sequence point: } - IL_007a: ret -}"); - c.VerifyPdb( -@" - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - } - [WorkItem(14437, "https://github.com/dotnet/roslyn/issues/14437")] [ConditionalFact(typeof(WindowsOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] public void LocalFunctionSequencePoints() diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs index faca23d8e3b6b..29b736989549b 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs @@ -92,12 +92,12 @@ public EncVariableSlotAllocator( public override DebugId? MethodId => _methodId; - private int CalculateSyntaxOffsetInPreviousMethod(int position, SyntaxTree tree) + private int CalculateSyntaxOffsetInPreviousMethod(SyntaxNode node) { // Note that syntax offset of a syntax node contained in a lambda body is calculated by the containing top-level method, // not by the lambda method. The offset is thus relative to the top-level method body start. We can thus avoid mapping // the current lambda symbol or body to the corresponding previous lambda symbol or body, which is non-trivial. - return _previousTopLevelMethod.CalculateLocalSyntaxOffset(position, tree); + return _previousTopLevelMethod.CalculateLocalSyntaxOffset(_lambdaSyntaxFacts.GetDeclaratorPosition(node), node.SyntaxTree); } public override void AddPreviousLocals(ArrayBuilder builder) @@ -120,11 +120,11 @@ private bool TryGetPreviousLocalId(SyntaxNode currentDeclarator, LocalDebugId cu SyntaxNode previousDeclarator = _syntaxMapOpt(currentDeclarator); if (previousDeclarator == null) { - previousId = default(LocalDebugId); + previousId = default; return false; } - int syntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousDeclarator.SpanStart, previousDeclarator.SyntaxTree); + int syntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousDeclarator); previousId = new LocalDebugId(syntaxOffset, currentId.Ordinal); return true; } @@ -246,7 +246,7 @@ private bool TryGetPreviousSyntaxOffset(SyntaxNode currentSyntax, out int previo return false; } - previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax.SpanStart, previousSyntax.SyntaxTree); + previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax); return true; } @@ -284,7 +284,7 @@ private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySynta previousSyntax = previousLambdaSyntax; } - previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax.SpanStart, previousSyntax.SyntaxTree); + previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax); return true; } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/LambdaSyntaxFacts.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/LambdaSyntaxFacts.cs index 5ccaaaaebcace..b585d2aba893e 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/LambdaSyntaxFacts.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/LambdaSyntaxFacts.cs @@ -15,5 +15,11 @@ internal abstract class LambdaSyntaxFacts /// JoinClause1.GetCorrespondingLambdaBody(JoinClause2.RightExpression) returns JoinClause1.RightExpression. ///

public abstract SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode previousLambdaSyntax, SyntaxNode lambdaOrLambdaBodySyntax); + + /// + /// Given a node that represents a variable declaration, lambda or a closure scope return the position to be used to calculate + /// the node's syntax offset with respect to its containing member. + /// + public abstract int GetDeclaratorPosition(SyntaxNode node); } } diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicLambdaSyntaxFacts.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicLambdaSyntaxFacts.vb index abdb4ec684ce4..571353970c39c 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicLambdaSyntaxFacts.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicLambdaSyntaxFacts.vb @@ -18,5 +18,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public Overrides Function TryGetCorrespondingLambdaBody(previousLambdaSyntax As SyntaxNode, lambdaOrLambdaBodySyntax As SyntaxNode) As SyntaxNode Return LambdaUtilities.GetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax, previousLambdaSyntax) End Function + + Public Overrides Function GetDeclaratorPosition(node As SyntaxNode) As Integer + Return node.SpanStart + End Function End Class End Namespace