File: Lowering\LocalRewriter\LocalRewriter_SwitchExpression.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class LocalRewriter
    {
        public override BoundNode VisitConvertedSwitchExpression(BoundConvertedSwitchExpression node)
        {
            // The switch expression is lowered to an expression that involves the use of side-effects
            // such as jumps and labels, therefore it is represented by a BoundSpillSequence and
            // the resulting nodes will need to be "spilled" to move such statements to the top
            // level (i.e. into the enclosing statement list).
            this._needsSpilling = true;
            return SwitchExpressionLocalRewriter.Rewrite(this, node);
        }
 
        private sealed class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter
        {
            private SwitchExpressionLocalRewriter(BoundConvertedSwitchExpression node, LocalRewriter localRewriter)
                : base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax),
                      generateInstrumentation: !node.WasCompilerGenerated && localRewriter.Instrument)
            {
            }
 
            public static BoundExpression Rewrite(LocalRewriter localRewriter, BoundConvertedSwitchExpression node)
            {
                var rewriter = new SwitchExpressionLocalRewriter(node, localRewriter);
                BoundExpression result = rewriter.LowerSwitchExpression(node);
                rewriter.Free();
                return result;
            }
 
            private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression node)
            {
                // When compiling for Debug (not Release), we produce the most detailed sequence points.
                var produceDetailedSequencePoints =
                    GenerateInstrumentation && _localRewriter._compilation.Options.OptimizationLevel != OptimizationLevel.Release;
                _factory.Syntax = node.Syntax;
                var result = ArrayBuilder<BoundStatement>.GetInstance();
                var outerVariables = ArrayBuilder<LocalSymbol>.GetInstance();
                var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression);
 
                BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput(
                    node.GetDecisionDagForLowering(_factory.Compilation, out LabelSymbol? defaultLabel),
                    loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression);
 
                Debug.Assert(savedInputExpression != null);
 
                object restorePointForEnclosingStatement = new object();
                object restorePointForSwitchBody = new object();
 
                // lower the decision dag.
                (ImmutableArray<BoundStatement> loweredDag, ImmutableDictionary<SyntaxNode, ImmutableArray<BoundStatement>> switchSections) =
                    LowerDecisionDag(decisionDag);
 
                if (_whenNodeIdentifierLocal is not null)
                {
                    outerVariables.Add(_whenNodeIdentifierLocal);
                }
 
                if (produceDetailedSequencePoints)
                {
                    var syntax = (SwitchExpressionSyntax)node.Syntax;
                    result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForEnclosingStatement));
                    // While evaluating the state machine, we highlight the `switch {...}` part.
                    var spanStart = syntax.SwitchKeyword.Span.Start;
                    var spanEnd = syntax.Span.End;
                    var spanForSwitchBody = new TextSpan(spanStart, spanEnd - spanStart);
                    result.Add(new BoundStepThroughSequencePoint(node.Syntax, span: spanForSwitchBody));
                    result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForSwitchBody));
                }
 
                // add the rest of the lowered dag that references that input
                result.Add(_factory.Block(loweredDag));
                // A branch to the default label when no switch case matches is included in the
                // 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.LoweringTemp);
                LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression");
                foreach (BoundSwitchExpressionArm arm in node.SwitchArms)
                {
                    _factory.Syntax = arm.Syntax;
                    var sectionBuilder = ArrayBuilder<BoundStatement>.GetInstance();
                    sectionBuilder.AddRange(switchSections[arm.Syntax]);
                    sectionBuilder.Add(_factory.Label(arm.Label));
                    var loweredValue = _localRewriter.VisitExpression(arm.Value);
                    if (GenerateInstrumentation)
                        loweredValue = this._localRewriter.Instrumenter.InstrumentSwitchExpressionArmExpression(arm.Value, loweredValue, _factory);
 
                    sectionBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), loweredValue));
                    sectionBuilder.Add(_factory.Goto(afterSwitchExpression));
                    var statements = sectionBuilder.ToImmutableAndFree();
                    if (arm.Locals.IsEmpty)
                    {
                        result.Add(_factory.StatementList(statements));
                    }
                    else
                    {
                        // Lifetime of these locals is expanded to the entire switch body, as it is possible to
                        // share them as temps in the decision dag.
                        outerVariables.AddRange(arm.Locals);
 
                        // Note the language scope of the locals, even though they are included for the purposes of
                        // lifetime analysis in the enclosing scope.
                        result.Add(new BoundScope(arm.Syntax, arm.Locals, statements));
                    }
                }
 
                _factory.Syntax = node.Syntax;
                if (defaultLabel is not null)
                {
                    result.Add(_factory.Label(defaultLabel));
                    if (produceDetailedSequencePoints)
                        result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForSwitchBody));
                    var objectType = _factory.SpecialType(SpecialType.System_Object);
                    var throwCall =
                        (implicitConversionExists(savedInputExpression, objectType) &&
                                _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol)
                            ? ConstructThrowSwitchExpressionExceptionHelperCall(_factory, _factory.Convert(objectType, savedInputExpression)) :
                        (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol)
                            ? ConstructThrowSwitchExpressionExceptionParameterlessHelperCall(_factory) :
                        ConstructThrowInvalidOperationExceptionHelperCall(_factory);
 
                    result.Add(throwCall);
                }
 
                if (GenerateInstrumentation)
                    result.Add(_factory.HiddenSequencePoint());
                result.Add(_factory.Label(afterSwitchExpression));
                if (produceDetailedSequencePoints)
                    result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForEnclosingStatement));
 
                outerVariables.Add(resultTemp);
                outerVariables.AddRange(_tempAllocator.AllTemps());
                return _factory.SpillSequence(outerVariables.ToImmutableAndFree(), result.ToImmutableAndFree(), _factory.Local(resultTemp));
 
                bool implicitConversionExists(BoundExpression expression, TypeSymbol type)
                {
                    var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                    Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, isChecked: false, ref discardedUseSiteInfo);
                    return c.IsImplicit;
                }
            }
 
            private static BoundStatement ConstructThrowSwitchExpressionExceptionHelperCall(SyntheticBoundNodeFactory factory, BoundExpression unmatchedValue)
            {
                Debug.Assert(factory.ModuleBuilderOpt is not null);
                var module = factory.ModuleBuilderOpt;
                var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
                var diagnostics = factory.Diagnostics.DiagnosticBag;
                Debug.Assert(diagnostics is not null);
                var throwSwitchExpressionExceptionMethod = module.EnsureThrowSwitchExpressionExceptionExists(diagnosticSyntax, factory, diagnostics);
                var call = factory.Call(
                    receiver: null,
                    throwSwitchExpressionExceptionMethod,
                    arg0: unmatchedValue);
                return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
            }
 
            private static BoundStatement ConstructThrowSwitchExpressionExceptionParameterlessHelperCall(SyntheticBoundNodeFactory factory)
            {
                Debug.Assert(factory.ModuleBuilderOpt is not null);
                var module = factory.ModuleBuilderOpt!;
                var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
                var diagnostics = factory.Diagnostics.DiagnosticBag;
                Debug.Assert(diagnostics is not null);
                var throwSwitchExpressionExceptionMethod = module.EnsureThrowSwitchExpressionExceptionParameterlessExists(diagnosticSyntax, factory, diagnostics);
                var call = factory.Call(
                    receiver: null,
                    throwSwitchExpressionExceptionMethod);
                return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
            }
 
            private static BoundStatement ConstructThrowInvalidOperationExceptionHelperCall(SyntheticBoundNodeFactory factory)
            {
                Debug.Assert(factory.ModuleBuilderOpt is not null);
                var module = factory.ModuleBuilderOpt!;
                var diagnosticSyntax = factory.CurrentFunction.GetNonNullSyntaxNode();
                var diagnostics = factory.Diagnostics.DiagnosticBag;
                Debug.Assert(diagnostics is not null);
                var throwMethod = module.EnsureThrowInvalidOperationExceptionExists(diagnosticSyntax, factory, diagnostics);
                var call = factory.Call(
                    receiver: null,
                    throwMethod);
                return factory.HiddenSequencePoint(factory.ExpressionStatement(call));
            }
        }
    }
}