File: FlowAnalysis\AbstractFlowPass_Switch.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.
 
#nullable disable
 
using System;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal abstract partial class AbstractFlowPass<TLocalState, TLocalFunctionState>
    {
        public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
        {
            // dispatch to the switch sections
            var afterSwitchState = VisitSwitchStatementDispatch(node);
 
            // visit switch sections
            var switchSections = node.SwitchSections;
            var iLastSection = (switchSections.Length - 1);
            for (var iSection = 0; iSection <= iLastSection; iSection++)
            {
                VisitSwitchSection(switchSections[iSection], iSection == iLastSection);
                // Even though it is illegal for the end of a switch section to be reachable, in erroneous
                // code it may be reachable.  We treat that as an implicit break (branch to afterSwitchState).
                Join(ref afterSwitchState, ref this.State);
            }
 
            ResolveBreaks(afterSwitchState, node.BreakLabel);
 
            return null;
        }
 
        protected virtual TLocalState VisitSwitchStatementDispatch(BoundSwitchStatement node)
        {
            // visit switch header
            VisitRvalue(node.Expression);
 
            TLocalState initialState = this.State.Clone();
 
            var reachableLabels = node.ReachabilityDecisionDag.ReachableLabels;
            foreach (var section in node.SwitchSections)
            {
                foreach (var label in section.SwitchLabels)
                {
                    if (reachableLabels.Contains(label.Label) || label.HasErrors ||
                        label == node.DefaultLabel && node.Expression.ConstantValueOpt == null && IsTraditionalSwitch(node))
                    {
                        SetState(initialState.Clone());
                    }
                    else
                    {
                        SetUnreachable();
                    }
 
                    VisitPattern(label.Pattern);
                    SetState(StateWhenTrue);
                    if (label.WhenClause != null)
                    {
                        VisitCondition(label.WhenClause);
                        SetState(StateWhenTrue);
                    }
 
                    PendingBranches.Add(new PendingBranch(label, this.State, label.Label));
                }
            }
 
            TLocalState afterSwitchState = UnreachableState();
            if (node.ReachabilityDecisionDag.ReachableLabels.Contains(node.BreakLabel) ||
                (node.DefaultLabel == null && node.Expression.ConstantValueOpt == null && IsTraditionalSwitch(node)))
            {
                Join(ref afterSwitchState, ref initialState);
            }
 
            return afterSwitchState;
        }
 
        /// <summary>
        /// Is the switch statement one that could be interpreted as a C# 6 or earlier switch statement?
        /// </summary>
        private bool IsTraditionalSwitch(BoundSwitchStatement node)
        {
            // Before recursive patterns were introduced, we did not consider handling both 'true' and 'false' to
            // completely handle all case of a switch on a bool unless there was some patterny syntax or semantics
            // in the switch.  We had two different bound nodes and separate flow analysis handling for
            // "traditional" switch statements and "pattern-based" switch statements.  We simulate that behavior
            // by testing to see if this switch would have been handled under the old rules by the old compiler.
 
            // If we are in a recent enough language version, we treat the switch as a fully pattern-based switch
            // for the purposes of flow analysis.
            if (compilation.LanguageVersion >= MessageID.IDS_FeatureRecursivePatterns.RequiredVersion())
            {
                return false;
            }
 
            if (!node.Expression.Type.IsValidV6SwitchGoverningType())
            {
                return false;
            }
 
            foreach (var sectionSyntax in ((SwitchStatementSyntax)node.Syntax).Sections)
            {
                foreach (var label in sectionSyntax.Labels)
                {
                    if (label.Kind() == SyntaxKind.CasePatternSwitchLabel)
                    {
                        return false;
                    }
                }
            }
 
            return true;
        }
 
        protected virtual void VisitSwitchSection(BoundSwitchSection node, bool isLastSection)
        {
            SetState(UnreachableState());
            foreach (var label in node.SwitchLabels)
            {
                VisitLabel(label.Label, node);
            }
 
            VisitStatementList(node);
        }
 
        public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node)
        {
            VisitRvalue(node.Expression);
            var state = this.State.Clone();
            PendingBranches.Add(new PendingBranch(node, state, node.DefaultLabel));
            foreach ((_, LabelSymbol label) in node.Cases)
            {
                PendingBranches.Add(new PendingBranch(node, state, label));
            }
 
            SetUnreachable();
            return null;
        }
 
        public override BoundNode VisitConvertedSwitchExpression(BoundConvertedSwitchExpression node)
        {
            return this.VisitSwitchExpression(node);
        }
 
        public override BoundNode VisitUnconvertedSwitchExpression(BoundUnconvertedSwitchExpression node)
        {
            return this.VisitSwitchExpression(node);
        }
 
        private BoundNode VisitSwitchExpression(BoundSwitchExpression node)
        {
            VisitRvalue(node.Expression);
            var dispatchState = this.State;
            var endState = UnreachableState();
            var reachableLabels = node.ReachabilityDecisionDag.ReachableLabels;
            foreach (var arm in node.SwitchArms)
            {
                SetState(dispatchState.Clone());
                VisitPattern(arm.Pattern);
                SetState(StateWhenTrue);
                if (!reachableLabels.Contains(arm.Label) || arm.Pattern.HasErrors)
                {
                    SetUnreachable();
                }
 
                if (arm.WhenClause != null)
                {
                    VisitCondition(arm.WhenClause);
                    SetState(StateWhenTrue);
                }
 
                VisitRvalue(arm.Value);
                Join(ref endState, ref this.State);
            }
 
            SetState(endState);
            return node;
        }
    }
}