Ejemplo n.º 1
0
        private void CheckSwitchErrors(
            SwitchStatementSyntax node,
            BoundExpression boundSwitchGoverningExpression,
            ref ImmutableArray <BoundSwitchSection> switchSections,
            BoundDecisionDag decisionDag,
            DiagnosticBag diagnostics)
        {
            var reachableLabels = decisionDag.ReachableLabels;

            bool isSubsumed(BoundSwitchLabel switchLabel)
            {
                return(!reachableLabels.Contains(switchLabel.Label));
            }

            // If no switch sections are subsumed, just return
            if (!switchSections.Any(s => s.SwitchLabels.Any(l => isSubsumed(l))))
            {
                return;
            }

            var sectionBuilder = ArrayBuilder <BoundSwitchSection> .GetInstance(switchSections.Length);

            foreach (var oldSection in switchSections)
            {
                var labelBuilder = ArrayBuilder <BoundSwitchLabel> .GetInstance(oldSection.SwitchLabels.Length);

                foreach (var label in oldSection.SwitchLabels)
                {
                    var newLabel = label;
                    if (!label.HasErrors && isSubsumed(label) && label.Syntax.Kind() != SyntaxKind.DefaultSwitchLabel)
                    {
                        var syntax = label.Syntax;
                        switch (syntax)
                        {
                        case CasePatternSwitchLabelSyntax p:
                            if (!p.Pattern.HasErrors)
                            {
                                diagnostics.Add(ErrorCode.ERR_SwitchCaseSubsumed, p.Pattern.Location);
                            }
                            break;

                        case CaseSwitchLabelSyntax p:
                            if (label.Pattern is BoundConstantPattern cp && !cp.ConstantValue.IsBad && FindMatchingSwitchCaseLabel(cp.ConstantValue, p) != label.Label)
                            {
                                // We use the traditional diagnostic when possible
                                diagnostics.Add(ErrorCode.ERR_DuplicateCaseLabel, syntax.Location, cp.ConstantValue.GetValueToDisplay());
                            }
                            else if (!label.Pattern.HasErrors)
                            {
                                diagnostics.Add(ErrorCode.ERR_SwitchCaseSubsumed, p.Value.Location);
                            }
                            break;
Ejemplo n.º 2
0
            /// <summary>
            /// Produce assignment of the input expression. This method is also responsible for assigning
            /// variables for some pattern-matching temps that can be shared with user variables.
            /// </summary>
            protected BoundDecisionDag ShareTempsAndEvaluateInput(
                BoundExpression loweredInput,
                BoundDecisionDag decisionDag,
                Action <BoundExpression> addCode,
                out BoundExpression savedInputExpression)
            {
                var inputDagTemp = BoundDagTemp.ForOriginalInput(loweredInput);

                if ((loweredInput.Kind == BoundKind.Local || loweredInput.Kind == BoundKind.Parameter) &&
                    loweredInput.GetRefKind() == RefKind.None)
                {
                    // If we're switching on a local variable and there is no when clause (checked by the caller),
                    // we assume the value of the local variable does not change during the execution of the
                    // decision automaton and we just reuse the local variable when we need the input expression.
                    // It is possible for this assumption to be violated by a side-effecting Deconstruct that
                    // modifies the local variable which has been captured in a lambda. Since the language assumes
                    // that functions called by pattern-matching are idempotent and not side-effecting, we feel
                    // justified in taking this assumption in the compiler too.
                    bool tempAssigned = _tempAllocator.TrySetTemp(inputDagTemp, loweredInput);
                    Debug.Assert(tempAssigned);
                }

                foreach (BoundDecisionDagNode node in decisionDag.TopologicallySortedNodes)
                {
                    if (node is BoundWhenDecisionDagNode w)
                    {
                        // We share a slot for a user-declared pattern-matching variable with a pattern temp if there
                        // is no user-written when-clause that could modify the variable before the matching
                        // automaton is done with it (checked by the caller).
                        foreach (BoundPatternBinding binding in w.Bindings)
                        {
                            if (binding.VariableAccess is BoundLocal l)
                            {
                                Debug.Assert(l.LocalSymbol.DeclarationKind == LocalDeclarationKind.PatternVariable);
                                _ = _tempAllocator.TrySetTemp(binding.TempContainingValue, binding.VariableAccess);
                            }
                        }
                    }
                }

                if (loweredInput.Type.IsTupleType &&
                    loweredInput.Syntax.Kind() == SyntaxKind.TupleExpression &&
                    loweredInput is BoundObjectCreationExpression expr &&
                    !decisionDag.TopologicallySortedNodes.Any(n => usesOriginalInput(n)))
                {
                    // If the switch governing expression is a tuple literal whose whole value is not used anywhere,
                    // (though perhaps its component parts are used), then we can save the component parts
                    // and assign them into temps (or perhaps user variables) to avoid the creation of
                    // the tuple altogether.
                    decisionDag = RewriteTupleInput(decisionDag, expr, addCode, out savedInputExpression);
                }
            internal BoundExpression LowerGeneralIsPattern(BoundIsPatternExpression node)
            {
                _factory.Syntax = node.Syntax;
                var resultBuilder = ArrayBuilder <BoundStatement> .GetInstance();

                var inputExpression          = _localRewriter.VisitExpression(node.Expression);
                BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput(
                    node.DecisionDag, inputExpression, resultBuilder, out _);

                // lower the decision dag.
                ImmutableArray <BoundStatement> loweredDag = LowerDecisionDagCore(decisionDag);

                resultBuilder.Add(_factory.Block(loweredDag));
                Debug.Assert(node.Type is { SpecialType: SpecialType.System_Boolean });
            private void ComputeLabelSet(BoundDecisionDag decisionDag)
            {
                // Nodes with more than one predecessor are assigned a label
                var hasPredecessor = PooledHashSet <BoundDecisionDagNode> .GetInstance();

                foreach (BoundDecisionDagNode node in decisionDag.TopologicallySortedNodes)
                {
                    switch (node)
                    {
                    case BoundWhenDecisionDagNode w:
                        GetDagNodeLabel(node);
                        if (w.WhenFalse != null)
                        {
                            GetDagNodeLabel(w.WhenFalse);
                        }
                        break;

                    case BoundLeafDecisionDagNode d:
                        // Leaf can branch directly to the target
                        _dagNodeLabels[node] = d.Label;
                        break;

                    case BoundEvaluationDecisionDagNode e:
                        notePredecessor(e.Next);
                        break;

                    case BoundTestDecisionDagNode p:
                        notePredecessor(p.WhenTrue);
                        notePredecessor(p.WhenFalse);
                        break;

                    default:
                        throw ExceptionUtilities.UnexpectedValue(node.Kind);
                    }
                }

                hasPredecessor.Free();
                return;

                void notePredecessor(BoundDecisionDagNode successor)
                {
                    if (successor != null && !hasPredecessor.Add(successor))
                    {
                        GetDagNodeLabel(successor);
                    }
                }
            }
Ejemplo n.º 5
0
        public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation)
        {
            BoundDecisionDag decisionDag = this.ReachabilityDecisionDag;

            if (decisionDag.ContainsAnySynthesizedNodes())
            {
                decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchStatement(
                    compilation,
                    this.Syntax,
                    this.Expression,
                    this.SwitchSections,
                    this.DefaultLabel?.Label ?? this.BreakLabel,
                    BindingDiagnosticBag.Discarded,
                    forLowering: true);
                Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes());
            }

            return(decisionDag);
        }
        internal override BoundStatement BindSwitchStatementCore(SwitchStatementSyntax node, Binder originalBinder, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(SwitchSyntax.Equals(node));

            if (node.Sections.Count == 0)
            {
                diagnostics.Add(ErrorCode.WRN_EmptySwitch, node.OpenBraceToken.GetLocation());
            }

            // Bind switch expression and set the switch governing type.
            BoundExpression boundSwitchGoverningExpression = SwitchGoverningExpression;

            diagnostics.AddRange(SwitchGoverningDiagnostics, allowMismatchInDependencyAccumulation: true);

            ImmutableArray <BoundSwitchSection>  switchSections = BindSwitchSections(originalBinder, diagnostics, out BoundSwitchLabel defaultLabel);
            ImmutableArray <LocalSymbol>         locals         = GetDeclaredLocalsForScope(node);
            ImmutableArray <LocalFunctionSymbol> functions      = GetDeclaredLocalFunctionsForScope(node);
            BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchStatement(
                compilation: this.Compilation,
                syntax: node,
                switchGoverningExpression: boundSwitchGoverningExpression,
                switchSections: switchSections,
                // If there is no explicit default label, the default action is to break out of the switch
                defaultLabel: defaultLabel?.Label ?? BreakLabel,
                diagnostics);

            // Report subsumption errors, but ignore the input's constant value for that.
            CheckSwitchErrors(node, boundSwitchGoverningExpression, ref switchSections, decisionDag, diagnostics);

            // When the input is constant, we use that to reshape the decision dag that is returned
            // so that flow analysis will see that some of the cases may be unreachable.
            decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(boundSwitchGoverningExpression);

            return(new BoundSwitchStatement(
                       syntax: node,
                       expression: boundSwitchGoverningExpression,
                       innerLocals: locals,
                       innerLocalFunctions: functions,
                       switchSections: switchSections,
                       defaultLabel: defaultLabel,
                       breakLabel: this.BreakLabel,
                       reachabilityDecisionDag: decisionDag));
        }
Ejemplo n.º 7
0
        public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation)
        {
            BoundDecisionDag decisionDag = this.ReachabilityDecisionDag;

            if (decisionDag.ContainsAnySynthesizedNodes())
            {
                decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern(
                    compilation,
                    this.Syntax,
                    this.Expression,
                    this.Pattern,
                    this.WhenTrueLabel,
                    this.WhenFalseLabel,
                    BindingDiagnosticBag.Discarded,
                    forLowering: true);
                Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes());
            }

            return(decisionDag);
        }
            protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput(
                BoundDecisionDag decisionDag,
                BoundExpression loweredSwitchGoverningExpression,
                ArrayBuilder <BoundStatement> result,
                out BoundExpression savedInputExpression)
            {
                // Note that a when-clause can contain an assignment to a
                // pattern variable declared in a different when-clause (e.g. in the same section, or
                // in a different section via the use of a local function), so we need to analyze all
                // of the when clauses to see if they are all simple enough to conclude that they do
                // not mutate pattern variables.
                var  mightAssignWalker = new WhenClauseMightAssignWalker();
                bool canShareTemps     =
                    !decisionDag.TopologicallySortedNodes
                    .Any(node => node is BoundWhenDecisionDagNode w && mightAssignWalker.MightAssignSomething(w.WhenExpression));

                if (canShareTemps)
                {
                    decisionDag = ShareTempsAndEvaluateInput(loweredSwitchGoverningExpression, decisionDag, expr => result.Add(_factory.ExpressionStatement(expr)), out savedInputExpression);
                }
                else
                {
                    // assign the input expression to its temp.
                    BoundExpression inputTemp = _tempAllocator.GetTemp(BoundDagTemp.ForOriginalInput(loweredSwitchGoverningExpression));
                    Debug.Assert(inputTemp != loweredSwitchGoverningExpression);
                    result.Add(_factory.Assignment(inputTemp, loweredSwitchGoverningExpression));
                    savedInputExpression = inputTemp;
                }

                // 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)
                {
                    result.Add(_factory.HiddenSequencePoint());
                }

                return(decisionDag);
            }
        public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation, out LabelSymbol?defaultLabel)
        {
            defaultLabel = this.DefaultLabel;

            BoundDecisionDag decisionDag = this.ReachabilityDecisionDag;

            if (decisionDag.ContainsAnySynthesizedNodes())
            {
                decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(
                    compilation,
                    this.Syntax,
                    this.Expression,
                    this.SwitchArms,
                    // there's no default label if the original switch is exhaustive.
                    // we generate a new label here because the new dag might not be.
                    defaultLabel ??= new GeneratedLabelSymbol("default"),
                    BindingDiagnosticBag.Discarded,
                    forLowering: true);
                Debug.Assert(!decisionDag.ContainsAnySynthesizedNodes());
            }

            return(decisionDag);
        }
Ejemplo n.º 10
0
            public BoundExpression LowerIsPattern(
                BoundIsPatternExpression isPatternExpression, BoundPattern pattern, CSharpCompilation compilation, DiagnosticBag diagnostics)
            {
                BoundDecisionDag decisionDag    = isPatternExpression.DecisionDag;
                LabelSymbol      whenTrueLabel  = isPatternExpression.WhenTrueLabel;
                LabelSymbol      whenFalseLabel = isPatternExpression.WhenFalseLabel;
                BoundExpression  loweredInput   = _localRewriter.VisitExpression(isPatternExpression.Expression);

                // The optimization of sharing pattern-matching temps with user variables can always apply to
                // an is-pattern expression because there is no when clause that could possibly intervene during
                // the execution of the pattern-matching automaton and change one of those variables.
                decisionDag = ShareTempsAndEvaluateInput(loweredInput, decisionDag, expr => _sideEffectBuilder.Add(expr), out _);
                var node = decisionDag.RootNode;

                // We follow the "good" path in the decision dag. We depend on it being nicely linear in structure.
                // If we add "or" patterns that assumption breaks down.
                while (node.Kind != BoundKind.LeafDecisionDagNode && node.Kind != BoundKind.WhenDecisionDagNode)
                {
                    switch (node)
                    {
                    case BoundEvaluationDecisionDagNode evalNode:
                    {
                        LowerOneTest(evalNode.Evaluation);
                        node = evalNode.Next;
                    }
                    break;

                    case BoundTestDecisionDagNode testNode:
                    {
                        Debug.Assert(testNode.WhenFalse is BoundLeafDecisionDagNode x && x.Label == whenFalseLabel);
                        if (testNode.WhenTrue is BoundEvaluationDecisionDagNode e &&
                            TryLowerTypeTestAndCast(testNode.Test, e.Evaluation, out BoundExpression sideEffect, out BoundExpression testExpression))
                        {
                            _sideEffectBuilder.Add(sideEffect);
                            AddConjunct(testExpression);
                            node = e.Next;
                        }
            private void LowerDecisionDagCore(BoundDecisionDag decisionDag)
            {
                ImmutableArray <BoundDecisionDagNode> sortedNodes = decisionDag.TopologicallySortedNodes;
                var firstNode = sortedNodes[0];

                switch (firstNode)
                {
                case BoundWhenDecisionDagNode _:
                case BoundLeafDecisionDagNode _:
                    // If the first node is a leaf or when clause rather than the code for the
                    // lowered decision dag, jump there to start.
                    _loweredDecisionDag.Add(_factory.Goto(GetDagNodeLabel(firstNode)));
                    break;
                }

                // Code for each when clause goes in the separate code section for its switch section.
                foreach (BoundDecisionDagNode node in sortedNodes)
                {
                    if (node is BoundWhenDecisionDagNode w)
                    {
                        LowerWhenClause(w);
                    }
                }

                ImmutableArray <BoundDecisionDagNode> nodesToLower = sortedNodes.WhereAsArray(n => n.Kind != BoundKind.WhenDecisionDagNode && n.Kind != BoundKind.LeafDecisionDagNode);
                var loweredNodes = PooledHashSet <BoundDecisionDagNode> .GetInstance();

                for (int i = 0, length = nodesToLower.Length; i < length; i++)
                {
                    BoundDecisionDagNode node = nodesToLower[i];
                    if (loweredNodes.Contains(node))
                    {
                        Debug.Assert(!_dagNodeLabels.TryGetValue(node, out _));
                        continue;
                    }

                    if (_dagNodeLabels.TryGetValue(node, out LabelSymbol? label))
                    {
                        _loweredDecisionDag.Add(_factory.Label(label));
                    }

                    // If we can generate an IL switch instruction, do so
                    if (GenerateSwitchDispatch(node, loweredNodes))
                    {
                        continue;
                    }

                    // If we can generate a type test and cast more efficiently as an `is` followed by a null check, do so
                    if (GenerateTypeTestAndCast(node, loweredNodes, nodesToLower, i))
                    {
                        continue;
                    }

                    // We pass the node that will follow so we can permit a test to fall through if appropriate
                    BoundDecisionDagNode?nextNode = ((i + 1) < length) ? nodesToLower[i + 1] : null;
                    if (nextNode != null && loweredNodes.Contains(nextNode))
                    {
                        nextNode = null;
                    }

                    LowerDecisionDagNode(node, nextNode);
                }

                loweredNodes.Free();
            }
            protected (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) LowerDecisionDag(BoundDecisionDag decisionDag)
            {
                Debug.Assert(this._loweredDecisionDag.IsEmpty());
                ComputeLabelSet(decisionDag);
                LowerDecisionDagCore(decisionDag);
                ImmutableArray <BoundStatement> loweredDag = _loweredDecisionDag.ToImmutableAndFree();
                var switchSections = _switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree());

                _switchArms.Clear();
                return(loweredDag, switchSections);
            }
Ejemplo n.º 13
0
        /// <summary>
        /// Build the decision dag, giving an error if some cases are subsumed and a warning if the switch expression is not exhaustive.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="boundInputExpression"></param>
        /// <param name="switchArms"></param>
        /// <param name="decisionDag"></param>
        /// <param name="diagnostics"></param>
        /// <returns>true if there was a non-exhaustive warning reported</returns>
        private bool CheckSwitchExpressionExhaustive(
            SwitchExpressionSyntax node,
            BoundExpression boundInputExpression,
            ImmutableArray <BoundSwitchExpressionArm> switchArms,
            out BoundDecisionDag decisionDag,
            out LabelSymbol defaultLabel,
            DiagnosticBag diagnostics)
        {
            defaultLabel = new GeneratedLabelSymbol("default");
            decisionDag  = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics);
            var reachableLabels = decisionDag.ReachableLabels;

            foreach (BoundSwitchExpressionArm arm in switchArms)
            {
                if (!reachableLabels.Contains(arm.Label))
                {
                    diagnostics.Add(ErrorCode.ERR_SwitchArmSubsumed, arm.Pattern.Syntax.Location);
                }
            }

            if (!reachableLabels.Contains(defaultLabel))
            {
                // switch expression is exhaustive; no default label needed.
                defaultLabel = null;
                return(false);
            }

            // We only report exhaustive warnings when the default label is reachable through some series of
            // tests that do not include a test in which the value is known to be null.  Handling paths with
            // nulls is the job of the nullable walker.
            foreach (var n in TopologicalSort.IterativeSort <BoundDecisionDagNode>(new[] { decisionDag.RootNode }, nonNullSuccessors))
            {
                if (n is BoundLeafDecisionDagNode leaf && leaf.Label == defaultLabel)
                {
                    diagnostics.Add(ErrorCode.WRN_SwitchExpressionNotExhaustive, node.SwitchKeyword.GetLocation());
                    return(true);
                }
            }

            return(false);

            ImmutableArray <BoundDecisionDagNode> nonNullSuccessors(BoundDecisionDagNode n)
            {
                switch (n)
                {
                case BoundTestDecisionDagNode p:
                    switch (p.Test)
                    {
                    case BoundDagNonNullTest t:         // checks that the input is not null
                        return(ImmutableArray.Create(p.WhenTrue));

                    case BoundDagExplicitNullTest t:         // checks that the input is null
                        return(ImmutableArray.Create(p.WhenFalse));

                    default:
                        return(BoundDecisionDag.Successors(n));
                    }

                default:
                    return(BoundDecisionDag.Successors(n));
                }
            }
        }
Ejemplo n.º 14
0
            private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression node)
            {
                _factory.Syntax = node.Syntax;
                var result = ArrayBuilder <BoundStatement> .GetInstance();

                var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance();

                var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression);
                BoundDecisionDag decisionDag         = ShareTempsIfPossibleAndEvaluateInput(
                    node.DecisionDag, loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression);

                Debug.Assert(savedInputExpression != null);

                // lower the decision dag.
                (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) =
                    LowerDecisionDag(decisionDag);

                // then 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));
                    sectionBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _localRewriter.VisitExpression(arm.Value)));
                    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 (node.DefaultLabel != null)
                {
                    result.Add(_factory.Label(node.DefaultLabel));
                    var objectType       = _factory.SpecialType(SpecialType.System_Object);
                    var thrownExpression =
                        (implicitConversionExists(savedInputExpression, objectType) &&
                         _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol exception1)
                            ? _factory.New(exception1, _factory.Convert(objectType, savedInputExpression)) :
                        (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol exception0)
                            ? _factory.New(exception0) :
                        _factory.New(_factory.WellKnownMethod(WellKnownMember.System_InvalidOperationException__ctor));
                    result.Add(_factory.Throw(thrownExpression));
                }

                result.Add(_factory.Label(afterSwitchExpression));
                outerVariables.Add(resultTemp);
                outerVariables.AddRange(_tempAllocator.AllTemps());
                return(_factory.SpillSequence(outerVariables.ToImmutableAndFree(), result.ToImmutableAndFree(), _factory.Local(resultTemp)));

                bool implicitConversionExists(BoundExpression expression, TypeSymbol type)
                {
                    HashSet <DiagnosticInfo>?discarded = null;
                    Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, ref discarded);

                    return(c.IsImplicit);
                }
            }
Ejemplo n.º 15
0
        private bool CheckSwitchExpressionExhaustive(
            SwitchExpressionSyntax node,
            BoundExpression boundInputExpression,
            ImmutableArray <BoundSwitchExpressionArm> switchArms,
            out BoundDecisionDag decisionDag,
            out LabelSymbol defaultLabel,
            BindingDiagnosticBag diagnostics)
        {
            defaultLabel = new GeneratedLabelSymbol("default");
            decisionDag  = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics);
            var  reachableLabels = decisionDag.ReachableLabels;
            bool hasErrors       = false;

            foreach (BoundSwitchExpressionArm arm in switchArms)
            {
                hasErrors |= arm.HasErrors;
                if (!hasErrors && !reachableLabels.Contains(arm.Label))
                {
                    diagnostics.Add(ErrorCode.ERR_SwitchArmSubsumed, arm.Pattern.Syntax.Location);
                }
            }

            if (!reachableLabels.Contains(defaultLabel))
            {
                // switch expression is exhaustive; no default label needed.
                defaultLabel = null;
                return(false);
            }

            if (hasErrors)
            {
                return(true);
            }

            // We only report exhaustive warnings when the default label is reachable through some series of
            // tests that do not include a test in which the value is known to be null.  Handling paths with
            // nulls is the job of the nullable walker.
            bool wasAcyclic = TopologicalSort.TryIterativeSort <BoundDecisionDagNode>(new[] { decisionDag.RootNode }, nonNullSuccessors, out var nodes);

            // Since decisionDag.RootNode is acyclic by construction, its subset of nodes sorted here cannot be cyclic
            Debug.Assert(wasAcyclic);
            foreach (var n in nodes)
            {
                if (n is BoundLeafDecisionDagNode leaf && leaf.Label == defaultLabel)
                {
                    var samplePattern = PatternExplainer.SamplePatternForPathToDagNode(
                        BoundDagTemp.ForOriginalInput(boundInputExpression), nodes, n, nullPaths: false, out bool requiresFalseWhenClause, out bool unnamedEnumValue);
                    ErrorCode warningCode =
                        requiresFalseWhenClause ? ErrorCode.WRN_SwitchExpressionNotExhaustiveWithWhen :
                        unnamedEnumValue ? ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue :
                        ErrorCode.WRN_SwitchExpressionNotExhaustive;
                    diagnostics.Add(
                        warningCode,
                        node.SwitchKeyword.GetLocation(),
                        samplePattern);
                    return(true);
                }
            }

            return(false);

            ImmutableArray <BoundDecisionDagNode> nonNullSuccessors(BoundDecisionDagNode n)
            {
                switch (n)
                {
                case BoundTestDecisionDagNode p:
                    switch (p.Test)
                    {
                    case BoundDagNonNullTest t:         // checks that the input is not null
                        return(ImmutableArray.Create(p.WhenTrue));

                    case BoundDagExplicitNullTest t:         // checks that the input is null
                        return(ImmutableArray.Create(p.WhenFalse));

                    default:
                        return(BoundDecisionDag.Successors(n));
                    }

                default:
                    return(BoundDecisionDag.Successors(n));
                }
            }
        }
Ejemplo n.º 16
0
            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.DecisionDag, 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 (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 (node.DefaultLabel != null)
                {
                    result.Add(_factory.Label(node.DefaultLabel));
                    if (produceDetailedSequencePoints)
                    {
                        result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForSwitchBody));
                    }
                    var objectType       = _factory.SpecialType(SpecialType.System_Object);
                    var thrownExpression =
                        (implicitConversionExists(savedInputExpression, objectType) &&
                         _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol exception1)
                            ? _factory.New(exception1, _factory.Convert(objectType, savedInputExpression)) :
                        (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol exception0)
                            ? _factory.New(exception0) :
                        _factory.New(_factory.WellKnownMethod(WellKnownMember.System_InvalidOperationException__ctor));
                    result.Add(_factory.Throw(thrownExpression));
                }

                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)
                {
                    HashSet <DiagnosticInfo>?discarded = null;
                    Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, ref discarded);

                    return(c.IsImplicit);
                }
            }
            private BoundStatement LowerSwitchStatement(BoundSwitchStatement node)
            {
                _factory.Syntax = node.Syntax;
                var result = ArrayBuilder <BoundStatement> .GetInstance();

                var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance();

                var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression);

                if (!node.WasCompilerGenerated && _localRewriter.Instrument)
                {
                    // EnC: We need to insert a hidden sequence point to handle function remapping in case
                    // the containing method is edited while methods invoked in the expression are being executed.
                    var instrumentedExpression = _localRewriter._instrumenter.InstrumentSwitchStatementExpression(node, loweredSwitchGoverningExpression, _factory);
                    if (loweredSwitchGoverningExpression.ConstantValue == null)
                    {
                        loweredSwitchGoverningExpression = instrumentedExpression;
                    }
                    else
                    {
                        // If the expression is a constant, we leave it alone (the decision dag lowering code needs
                        // to see that constant). But we add an additional leading statement with the instrumented expression.
                        result.Add(_factory.ExpressionStatement(instrumentedExpression));
                    }
                }

                // The set of variables attached to the outer block
                outerVariables.AddRange(node.InnerLocals);

                // Evaluate the input and set up sharing for dag temps with user variables
                BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput(node.DecisionDag, loweredSwitchGoverningExpression, result, out _);

                // lower the decision dag.
                (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) =
                    LowerDecisionDag(decisionDag);

                // then 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 dag, so the code in `result` is unreachable at this point.

                // Lower each switch section.
                foreach (BoundSwitchSection section in node.SwitchSections)
                {
                    _factory.Syntax = section.Syntax;
                    var sectionBuilder = ArrayBuilder <BoundStatement> .GetInstance();

                    sectionBuilder.AddRange(switchSections[section.Syntax]);
                    foreach (BoundSwitchLabel switchLabel in section.SwitchLabels)
                    {
                        sectionBuilder.Add(_factory.Label(switchLabel.Label));
                    }

                    // Add the translated body of the switch section
                    sectionBuilder.AddRange(_localRewriter.VisitList(section.Statements));

                    // By the semantics of the switch statement, the end of each section is required to be unreachable.
                    // So we can just seal the block and there is no need to follow it by anything.
                    ImmutableArray <BoundStatement> statements = sectionBuilder.ToImmutableAndFree();

                    if (section.Locals.IsEmpty)
                    {
                        result.Add(_factory.StatementList(statements));
                    }
                    else
                    {
                        // Lifetime of these locals is expanded to the entire switch body, as it is possible to capture
                        // them in a different section by using a local function as an intermediary.
                        outerVariables.AddRange(section.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(section.Syntax, section.Locals, statements));
                    }
                }

                // Dispatch temps are in scope throughout the switch statement, as they are used
                // both in the dispatch section to hold temporary values from the translation of
                // the decision dag, and in the branches where the temp values are assigned to the
                // pattern variables of matched patterns.
                outerVariables.AddRange(_tempAllocator.AllTemps());

                _factory.Syntax = node.Syntax;
                result.Add(_factory.Label(node.BreakLabel));
                BoundStatement translatedSwitch = _factory.Block(outerVariables.ToImmutableAndFree(), node.InnerLocalFunctions, result.ToImmutableAndFree());

                // Only add instrumentation (such as a sequence point) if the node is not compiler-generated.
                if (!node.WasCompilerGenerated && _localRewriter.Instrument)
                {
                    translatedSwitch = _localRewriter._instrumenter.InstrumentSwitchStatement(node, translatedSwitch);
                }

                return(translatedSwitch);
            }
Ejemplo n.º 18
0
        LearnFromDecisionDag(
            SyntaxNode node,
            BoundDecisionDag decisionDag,
            BoundExpression expression,
            TypeWithState expressionType,
            ref LocalState initialState)
        {
            // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are
            // not copying the input to evaluate the patterns.  In this way we infer non-nullability of the original
            // variable's parts based on matched pattern parts.  Mutations in `when` clauses can show the inaccuracy
            // of analysis based on this choice.
            var rootTemp          = BoundDagTemp.ForOriginalInput(expression);
            int originalInputSlot = MakeSlot(expression);

            if (originalInputSlot <= 0)
            {
                originalInputSlot = makeDagTempSlot(expressionType.ToTypeWithAnnotations(), rootTemp);
                initialState[originalInputSlot] = expressionType.State;
            }

            var tempMap = PooledDictionary <BoundDagTemp, (int slot, TypeSymbol type)> .GetInstance();

            Debug.Assert(originalInputSlot > 0);
            tempMap.Add(rootTemp, (originalInputSlot, expressionType.Type));

            var nodeStateMap = PooledDictionary <BoundDecisionDagNode, (LocalState state, bool believedReachable)> .GetInstance();

            nodeStateMap.Add(decisionDag.RootNode, (state: initialState.Clone(), believedReachable: true));

            var labelStateMap = PooledDictionary <LabelSymbol, (LocalState state, bool believedReachable)> .GetInstance();

            foreach (var dagNode in decisionDag.TopologicallySortedNodes)
            {
                bool found = nodeStateMap.TryGetValue(dagNode, out var nodeStateAndBelievedReachable);
                Debug.Assert(found); // the topologically sorted nodes should contain only reachable nodes
                (LocalState nodeState, bool nodeBelievedReachable) = nodeStateAndBelievedReachable;
                SetState(nodeState);

                switch (dagNode)
                {
                case BoundEvaluationDecisionDagNode p:
                {
                    var evaluation = p.Evaluation;
                    (int inputSlot, TypeSymbol inputType) = tempMap.TryGetValue(evaluation.Input, out var slotAndType) ? slotAndType : throw ExceptionUtilities.Unreachable;
                    Debug.Assert(inputSlot > 0);
                    var inputState = this.State[inputSlot];

                    switch (evaluation)
                    {
                    case BoundDagDeconstructEvaluation e:
                    {
                        // https://github.com/dotnet/roslyn/issues/34232
                        // We may need to recompute the Deconstruct method for a deconstruction if
                        // the receiver type has changed (e.g. its nested nullability).
                        var method         = e.DeconstructMethod;
                        int extensionExtra = method.IsStatic ? 1 : 0;
                        for (int i = 0; i < method.ParameterCount - extensionExtra; i++)
                        {
                            var parameterType = method.Parameters[i + extensionExtra].TypeWithAnnotations;
                            var output        = new BoundDagTemp(e.Syntax, parameterType.Type, e, i);
                            int outputSlot    = makeDagTempSlot(parameterType, output);
                            Debug.Assert(outputSlot > 0);
                            addToTempMap(output, outputSlot, parameterType.Type);
                        }
                        break;
                    }

                    case BoundDagTypeEvaluation e:
                    {
                        var output = new BoundDagTemp(e.Syntax, e.Type, e);
                        HashSet <DiagnosticInfo> discardedDiagnostics = null;
                        int outputSlot;
                        switch (_conversions.WithNullability(false).ClassifyConversionFromType(inputType, e.Type, ref discardedDiagnostics).Kind)
                        {
                        case ConversionKind.Identity:
                        case ConversionKind.ImplicitReference:
                        case ConversionKind.NoConversion:
                        case ConversionKind.ExplicitReference:
                            outputSlot = inputSlot;
                            break;

                        case ConversionKind.ExplicitNullable when AreNullableAndUnderlyingTypes(inputType, e.Type, out _):
                            outputSlot = GetNullableOfTValueSlot(inputType, inputSlot, out _, forceSlotEvenIfEmpty: true);

                            if (outputSlot < 0)
                            {
                                goto default;
                            }
                            break;

                        default:
                            outputSlot = makeDagTempSlot(TypeWithAnnotations.Create(e.Type, NullableAnnotation.NotAnnotated), output);
                            break;
                        }
                        State[outputSlot] = NullableFlowState.NotNull;
                        var outputType = TypeWithState.Create(e.Type, inputState);
                        addToTempMap(output, outputSlot, outputType.Type);
                        break;
                    }

                    case BoundDagFieldEvaluation e:
                    {
                        Debug.Assert(inputSlot > 0);
                        var field      = (FieldSymbol)AsMemberOfType(inputType, e.Field);
                        int outputSlot = GetOrCreateSlot(field, inputSlot, forceSlotEvenIfEmpty: true);
                        Debug.Assert(outputSlot > 0);
                        var type   = field.Type;
                        var output = new BoundDagTemp(e.Syntax, type, e);
                        addToTempMap(output, outputSlot, type);
                        break;
                    }

                    case BoundDagPropertyEvaluation e:
                    {
                        Debug.Assert(inputSlot > 0);
                        var property   = (PropertySymbol)AsMemberOfType(inputType, e.Property);
                        var type       = property.TypeWithAnnotations;
                        var output     = new BoundDagTemp(e.Syntax, type.Type, e);
                        int outputSlot = GetOrCreateSlot(property, inputSlot, forceSlotEvenIfEmpty: true);
                        if (outputSlot <= 0)
                        {
                            // This is needed due to https://github.com/dotnet/roslyn/issues/29619
                            outputSlot = makeDagTempSlot(type, output);
                        }
                        Debug.Assert(outputSlot > 0);
                        addToTempMap(output, outputSlot, type.Type);
                        break;
                    }

                    case BoundDagIndexEvaluation e:
                    {
                        var type       = TypeWithAnnotations.Create(e.Property.Type, NullableAnnotation.Annotated);
                        var output     = new BoundDagTemp(e.Syntax, type.Type, e);
                        int outputSlot = makeDagTempSlot(type, output);
                        Debug.Assert(outputSlot > 0);
                        addToTempMap(output, outputSlot, type.Type);
                        break;
                    }

                    default:
                        throw ExceptionUtilities.UnexpectedValue(p.Evaluation.Kind);
                    }
                    gotoNode(p.Next, this.State, nodeBelievedReachable);
                    break;
                }

                case BoundTestDecisionDagNode p:
                {
                    var  test      = p.Test;
                    bool foundTemp = tempMap.TryGetValue(test.Input, out var slotAndType);
                    Debug.Assert(foundTemp);

                    (int inputSlot, TypeSymbol inputType) = slotAndType;
                    var inputState = this.State[inputSlot];
                    Split();
                    switch (test)
                    {
                    case BoundDagTypeTest t:
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    case BoundDagNonNullTest t:
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable & inputState.MayBeNull());
                        break;

                    case BoundDagExplicitNullTest t:
                        if (inputSlot > 0)
                        {
                            LearnFromNullTest(inputSlot, inputType, ref this.StateWhenTrue);
                            learnFromNonNullTest(inputSlot, ref this.StateWhenFalse);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    case BoundDagValueTest t:
                        Debug.Assert(t.Value != ConstantValue.Null);
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    default:
                        throw ExceptionUtilities.UnexpectedValue(test.Kind);
                    }
                    break;
                }

                case BoundLeafDecisionDagNode d:
                    // We have one leaf decision dag node per reachable label
                    labelStateMap.Add(d.Label, (this.State, nodeBelievedReachable));
                    break;

                case BoundWhenDecisionDagNode w:
                    // bind the pattern variables, inferring their types as well
                    foreach (var binding in w.Bindings)
                    {
                        var variableAccess = binding.VariableAccess;
                        var tempSource     = binding.TempContainingValue;
                        var foundTemp      = tempMap.TryGetValue(tempSource, out var tempSlotAndType);
                        Debug.Assert(foundTemp);
                        var(tempSlot, tempType) = tempSlotAndType;
                        var tempState = this.State[tempSlot];
                        if (variableAccess is BoundLocal {
                            LocalSymbol: SourceLocalSymbol {
                                IsVar: true
                            } local
                        })
                        {
                            var inferredType = TypeWithState.Create(tempType, tempState).ToTypeWithAnnotations();
                            if (_variableTypes.TryGetValue(local, out var existingType))
                            {
                                // merge inferred nullable annotation from different branches of the decision tree
                                _variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation));
                            }
                            else
                            {
                                _variableTypes[local] = inferredType;
                            }

                            int localSlot = GetOrCreateSlot(local, forceSlotEvenIfEmpty: true);
                            this.State[localSlot] = tempState;
                        }
            protected ImmutableArray <BoundStatement> LowerDecisionDagCore(BoundDecisionDag decisionDag)
            {
                _loweredDecisionDag = ArrayBuilder <BoundStatement> .GetInstance();

                ComputeLabelSet(decisionDag);
                ImmutableArray <BoundDecisionDagNode> sortedNodes = decisionDag.TopologicallySortedNodes;
                var firstNode = sortedNodes[0];

                switch (firstNode)
                {
                case BoundWhenDecisionDagNode _:
                case BoundLeafDecisionDagNode _:
                    // If the first node is a leaf or when clause rather than the code for the
                    // lowered decision dag, jump there to start.
                    _loweredDecisionDag.Add(_factory.Goto(GetDagNodeLabel(firstNode)));
                    break;
                }

                // Code for each when clause goes in the separate code section for its switch section.
                foreach (BoundDecisionDagNode node in sortedNodes)
                {
                    if (node is BoundWhenDecisionDagNode w)
                    {
                        LowerWhenClause(w);
                    }
                }

                ImmutableArray <BoundDecisionDagNode> nodesToLower = sortedNodes.WhereAsArray(n => n.Kind != BoundKind.WhenDecisionDagNode && n.Kind != BoundKind.LeafDecisionDagNode);
                var loweredNodes = PooledHashSet <BoundDecisionDagNode> .GetInstance();

                for (int i = 0, length = nodesToLower.Length; i < length; i++)
                {
                    BoundDecisionDagNode node = nodesToLower[i];
                    // A node may have been lowered as part of a switch dispatch, but if it had a label, we'll need to lower it individually as well
                    bool alreadyLowered = loweredNodes.Contains(node);
                    if (alreadyLowered && !_dagNodeLabels.TryGetValue(node, out _))
                    {
                        continue;
                    }

                    if (_dagNodeLabels.TryGetValue(node, out LabelSymbol label))
                    {
                        _loweredDecisionDag.Add(_factory.Label(label));
                    }

                    // If we can generate an IL switch instruction, do so
                    if (!alreadyLowered && GenerateSwitchDispatch(node, loweredNodes))
                    {
                        continue;
                    }

                    // If we can generate a type test and cast more efficiently as an `is` followed by a null check, do so
                    if (GenerateTypeTestAndCast(node, loweredNodes, nodesToLower, i))
                    {
                        continue;
                    }

                    // We pass the node that will follow so we can permit a test to fall through if appropriate
                    BoundDecisionDagNode nextNode = ((i + 1) < length) ? nodesToLower[i + 1] : null;
                    if (nextNode != null && loweredNodes.Contains(nextNode))
                    {
                        nextNode = null;
                    }

                    LowerDecisionDagNode(node, nextNode);
                }

                loweredNodes.Free();
                var result = _loweredDecisionDag.ToImmutableAndFree();

                _loweredDecisionDag = null;
                return(result);
            }