public override void VisitTry(BoundTry node)
            {
                // Rules for try/catch:
                // * Try stays the default;
                // * Catch becomes a fork (it's only taken when an exception is
                //   thrown);
                // * Finally is treated special. It's also executed on the default
                //   branch, but it's also executed from break/continue/throw/return's,
                //   because these flow through the finally. We don't do this here,
                //   but in JoinOnBlock.

                var block = new Block(_branch, null, false, false, node.Finally);

                PushBlock(block);

                Visit(node.Try);

                if (node.Catch != null)
                {
                    // Create a block for the catch.

                    var catchBlock = new Block(block.Branch, null, false, false, null);

                    PushBlock(catchBlock);

                    _branch = catchBlock.Branch.Fork();

                    Visit(node.Catch);

                    catchBlock.JoinBranch(_branch);

                    // Create an empty fork to tell the Join algorithm that the
                    // Catch was an optional branch.

                    catchBlock.JoinBranch(catchBlock.Branch.Fork());

                    PopBlock(catchBlock);
                }

                Visit(node.Finally);

                PopBlock(block);
            }
            public override void VisitWhile(BoundWhile node)
            {
                // Rules for while:
                // * When IsFalse(Test), we don't have any work;
                // * When IsTrue(Test), _branch stays the default;
                // * Otherwise, _branch becomes a fork;
                // * Target for break/continue.

                var result = ToBoolean(node.Test);

                // If Test evaluates to a constant expression false, the while
                // loop is never taken.

                if (result == false)
                    return;

                // The Test is executed on the default branch.

                Visit(node.Test);

                var block = new Block(_branch, FindLabel(node), true, true, null);

                PushBlock(block);

                _branch = block.Branch.Fork();

                Visit(node.Body);

                block.JoinBranch(_branch);

                // Create an empty fork to tell the Join algorithm that the
                // Body was an optional branch.

                if (result != true)
                    block.JoinBranch(block.Branch.Fork());

                PopBlock(block);
            }
            public override void VisitSwitch(BoundSwitch node)
            {
                // Rules for switch:
                // * Target for break;
                // * If we don't have any cases, there is no work;
                // * If we only have a default case, _branch stays the default
                //   and there is no special handling;
                // * Otherwise, we fully process the cases. Things become a bit
                //   tricky in a switch because of label fall through.
                //   Cases really are nested ifs. Say we have the following:
                //
                //     switch (x) {
                //     case 1:
                //       a();
                //     case 2:
                //       b();
                //       break;
                //     }
                //
                //   This can be rewritten as
                //
                //     if (x == 1 || x == 2) {
                //       if (x == 1) {
                //         a();
                //       }
                //       b();
                //     }
                //
                //   With this rewrite, we can construct cases as follows:
                //     - Every case is a fork;
                //     - If the branch isn't killed, we create an extra fork
                //       (the else of the 'if (x == 1)' above);
                //     - Otherwise, we create a new fork for the next case.
                //     - Default is treated like any other case; except that when
                //       we don't have a default, we create an extra fork at the
                //       end of the switch.
                //
                // One extra note: we don't have an expression here. The bound
                // tree has already introduced a temporary for the result of the
                // expression, so we don't have any handling for that here.

                // Mark a read on the temporary.
                MarkRead(node.Temporary);

                // No cases means no work.
                if (node.Cases.Count == 0)
                    return;

                // When we only have a default case, we can just take the default.
                // However, we're still a break target so we do need to create
                // a block.
                if (node.Cases.Count == 1 && node.Cases[0].Expression == null)
                {
                    var block = new Block(_branch, FindLabel(node), true, false, null);

                    PushBlock(block);

                    base.VisitSwitch(node);

                    PopBlock(block);

                    return;
                }

                bool hadDefault = false;

                // Push the block for the switch.

                var switchBlock = new Block(_branch, FindLabel(node), true, false, null);

                PushBlock(switchBlock);

                // Clear the branch to signal the creation of a new branch.

                BoundTypeManager.DefiniteAssignmentMarker.Branch caseBranch = null;

                _branch = null;

                for (int i = 0; i < node.Cases.Count; i++)
                {
                    var @case = node.Cases[i];

                    if (@case.Expression == null)
                        hadDefault = true;

                    // Create a new branch for a series of cases (either the
                    // first one or on fall through).

                    if (caseBranch == null)
                        caseBranch = switchBlock.Branch.Fork();

                    // Create a new branch for the contents of this case.

                    _branch = caseBranch.Fork();

                    Visit(@case);

                    // Do we have a fall through (and we're not the last, because
                    // that doesn't fall through)?

                    if (!_branch.IsKilled && i != node.Cases.Count - 1)
                    {
                        // If we have a fall through, join the branch
                        // and an empty branch to close the current case.
                        // This makes this case optional.

                        caseBranch.Join(new[] { _branch, caseBranch.Fork() });
                    }
                    else
                    {
                        // If we don't have a fall through, check whether the
                        // branch is killed. If it isn't killed, we must be the
                        // last case and it's missing the break, which we insert
                        // here.

                        if (!_branch.IsKilled)
                        {
                            Debug.Assert(i == node.Cases.Count - 1);

                            JoinOnBlock(null, JoinType.Break);
                        }

                        // The case branch has already been joined on the switch
                        // block. Close this case and signal the creation of a new
                        // branch.

                        caseBranch = null;
                    }
                }

                // If we didn't have a default case, we need to create an empty
                // branch for the else.

                if (!hadDefault)
                    switchBlock.JoinBranch(switchBlock.Branch.Fork());

                // And pop the switch block.

                PopBlock(switchBlock);
            }
            public override void VisitIf(BoundIf node)
            {
                // Rules for if:
                // * When IsTrue(Test), Else is not taken and Then stays the
                //   default branch;
                // * When IsFalse(Test), Then is not taken and If stays the
                //   default branch;
                // * Otherwise, Then and Else become a fork (even when Else
                //   is null).

                bool? result = ToBoolean(node.Test);

                // If Test evaluates to a constant that allows us to skip
                // the Then, and we don't have an Else, we don't have any
                // work for this node.

                if (result == false && node.Else == null)
                    return;

                // Test is executed on the default branch.
                Visit(node.Test);

                var block = new Block(_branch, null, false, false, null);

                PushBlock(block);

                if (!result.HasValue)
                {
                    // Visit the Then.

                    _branch = block.Branch.Fork();

                    Visit(node.Then);

                    block.JoinBranch(_branch);

                    // Visit the Else. Note that we always fork the branch, even
                    // when we don't have an Else. The reason for this is that
                    // the join algorithm correctly marks variables that are
                    // only written to in the Then branch as not definitely
                    // assigned.

                    _branch = block.Branch.Fork();

                    if (node.Else != null)
                        Visit(node.Else);

                    block.JoinBranch(_branch);
                }
                else
                {
                    Visit(result.Value ? node.Then : node.Else);
                }

                PopBlock(block);
            }
            public override void VisitForEachIn(BoundForEachIn node)
            {
                // Rules for for each in:
                // * _branch becomes a fork and we create an empty fork;
                // * Target for break/continue.

                // Expression is executed on the default branch.
                Visit(node.Expression);

                MarkWrite(node.Target);

                var block = new Block(_branch, FindLabel(node), true, true, null);

                PushBlock(block);

                _branch = block.Branch.Fork();

                Visit(node.Body);

                block.JoinBranch(_branch);

                // Create an empty branch to signal that the body is optional.

                block.JoinBranch(block.Branch.Fork());

                PopBlock(block);
            }
            public override void VisitFor(BoundFor node)
            {
                // Rules for for:
                // * _branch stays default when IsTrue(Test); otherwise _branch
                //   becomes a fork;
                // * Target for break/continue;
                // * Increment is only executed when the default branch reaches
                //   the end or a continue was executed.

                // Initialization and test are executed on the current branch.
                Visit(node.Initialization);
                Visit(node.Test);

                var block = new Block(_branch, FindLabel(node), true, true, null);

                _branch = block.Branch.Fork();

                PushBlock(block);

                Visit(node.Body);

                bool wasKilled = _branch.IsKilled;

                // Only visit the increment when then default branch flows there
                // or a continue was executed.
                if (!_branch.IsKilled || block.ContinueTaken)
                {
                    // We need to restore the branch because if the branch is
                    // killed, but we have a continue, the test is still done on
                    // the branch.
                    _branch.IsKilled = false;

                    Visit(node.Increment);
                }

                if (!wasKilled)
                    block.JoinBranch(_branch);

                // Create an extra empty branch when the test wasn't unconditional.
                if (node.Test != null && ToBoolean(node.Test) != true)
                    block.JoinBranch(block.Branch.Fork());

                PopBlock(block);
            }
            public override void VisitDoWhile(BoundDoWhile node)
            {
                // Rules for do/while:
                // * _branch stays default branch (we always take the do);
                // * Target for break/continue;
                // * Test is only executed when the default branch reaches the end
                //   or a continue was executed.

                var block = new Block(_branch, FindLabel(node), true, true, null);

                PushBlock(block);

                _branch = block.Branch.Fork();

                Visit(node.Body);

                bool wasKilled = _branch.IsKilled;

                if (!_branch.IsKilled || block.ContinueTaken)
                {
                    // We need to restore the branch because if the branch is
                    // killed, but we have a continue, the test is still done on
                    // the branch.
                    _branch.IsKilled = false;

                    Visit(node.Test);
                }

                if (!wasKilled)
                    block.JoinBranch(_branch);

                PopBlock(block);
            }