fn preconditions(cfg: &Cfg, constraint: Constraint) -> Dnf {
    return match constraint {
        Constraint::Passable(node) => passable_preconditions(cfg, node),
        Constraint::Haltable(node) => haltable_preconditions(cfg, node),
        Constraint::BreakableTo {
            breaks_from,
            breaks_to,
        } => breakable_to_preconditions(cfg, breaks_from, breaks_to),
    };

    /// Compute the preconditions for a `Passable` constraint.
    fn passable_preconditions(cfg: &Cfg, node: Index) -> Dnf {
        match &cfg[node].expr {
            // Trivially, `Send`, `Recv`, and `Call(None)` nodes are passable. We also assume that
            // for any `Type` node, the external type it references is passable, and that for any
            // `Error` node, assuming it passable will give the most possible relevant errors
            // reported.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error | Ir::Call(None) => {
                Dnf::trivially_true()
            }
            // Trivially, `Break` and `Continue` nodes are never passable, because they always jump
            // and never call their continuations.
            Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
            // `Call` nodes are only passable if their continuations are haltable.
            Ir::Call(Some(callee)) => Dnf::only_if(vec![Constraint::Haltable(*callee)]),
            // Similarly to call, split nodes are only passable if both arms are haltable.
            Ir::Split { tx_only, rx_only } => {
                let arms = tx_only.iter().chain(rx_only);
                Dnf::only_if(arms.map(|&arm| Constraint::Haltable(arm)).collect())
            }
            // `Offer` and `Choose` nodes are passable if any arm is passable; however, the
            // passability of an `Offer` or `Choose` typically does not matter because as control
            // flow analysis must be run after scope resolution, they should not have an implicit
            // continuation.
            Ir::Offer(choices) | Ir::Choose(choices) => Dnf(choices
                .iter()
                .filter_map(Option::as_ref)
                .map(|&c| vec![Constraint::Passable(c)])
                .collect()),
            // The `Loop` case is the most complex case here. A `Loop` only has its continuation
            // called when a `Break` occurs targeting the loop in question, so it is only passable
            // if it is `BreakableTo` from some node inside it.
            Ir::Loop(body) => Dnf::only_if(vec![Constraint::BreakableTo {
                breaks_from: body
                    .expect("loop bodies should always be `Some` after scope resolution"),
                breaks_to: node,
            }]),
        }
    }

    /// Compute the preconditions for a `Haltable` constraint.
    fn haltable_preconditions(cfg: &Cfg, node_index: Index) -> Dnf {
        let node = &cfg[node_index];
        match &node.expr {
            // Trivially, `Send`, `Recv`, and `Type` are only haltable if they continue to a
            // haltable node (as they always continue.) We also assume `Error` behaves this way to
            // try to get the maximum possible number of reported relevant errors.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
                Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
                None => Dnf::trivially_true(),
            },
            // If a node is passable and its continuation halts, then it is always haltable. Instead
            // of spitting out constraints on the child nodes of these cases, we rely on their
            // definition of passability. Note that if the continuation is `None`, then the node
            // halts only if it is passable, so in that case a `Haltable` constraint on it is not
            // generated.
            Ir::Call(_) | Ir::Split { .. } | Ir::Loop(_) => {
                let mut conj = vec![Constraint::Passable(node_index)];
                conj.extend(node.next.map(Constraint::Haltable));
                Dnf::only_if(conj)
            }
            // `Choose`/`Offer` are haltable only if any of their choices are haltable.
            Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
                .iter()
                // If any of the choices are `Done`, we want to emit an empty `Vec` instead,
                // denoting that this constraint is trivially satisfiable.
                .map(|&c| c.map(Constraint::Haltable).into_iter().collect())
                .chain(node.next.map(|c| vec![Constraint::Haltable(c)]))
                .collect()),
            // A `Continue` only halts if whatever it jumps to halts.
            Ir::Continue(of_loop) => Dnf::only_if(vec![Constraint::Haltable(*of_loop)]),
            // Similarly, a `Break` is a kind of jump to the continuation of its relevant loop, and
            // halts only if the continuation of the loop halts.
            Ir::Break(of_loop) => match cfg[*of_loop].next {
                Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
                None => Dnf::trivially_true(),
            },
        }
    }

    /// Compute the preconditions for a `BreakableTo` constraint.
    fn breakable_to_preconditions(cfg: &Cfg, breaks_from: Index, breaks_to: Index) -> Dnf {
        let node = &cfg[breaks_from];
        // For most nodes, `BreakableTo` is inductive on its continuation.
        match &node.expr {
            // Trivially, `Send`, `Recv`, and `Type` will only break to a loop if their continuation
            // does... as obviously they do not break. Though its behavior is undefined, we assume
            // `Error` also behaves this way.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
                Some(cont) => Dnf::only_if(vec![Constraint::BreakableTo {
                    breaks_from: cont,
                    breaks_to,
                }]),
                None => Dnf::trivially_false(),
            },
            // `Call` and `Split` may break from within, but that doesn't actually count as them
            // being breakable to a loop. Instead, similarly to `Send`, `Recv`, and `Type`, they are
            // `BreakableTo` only if their continuation is, but we must also know whether they are
            // passable (so that we can know if control flow can reach that break if it is present.)
            Ir::Call(_) | Ir::Split { .. } => {
                let mut conj = vec![Constraint::Passable(breaks_from)];
                conj.extend(node.next.map(|cont| Constraint::BreakableTo {
                    breaks_from: cont,
                    breaks_to,
                }));
                Dnf::only_if(conj)
            }
            // `Choose` and `Offer` break only if any arm breaks.
            Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
                .iter()
                .filter_map(Option::as_ref)
                .chain(node.next.as_ref())
                .map(|&c| {
                    vec![Constraint::BreakableTo {
                        breaks_from: c,
                        breaks_to,
                    }]
                })
                .collect()),
            // A loop only breaks to another loop in one of two cases: 1.) it contains a break
            // targeting the loop in question. 2.) it is passable, and once it is passed, its
            // continuation may break to the target.
            Ir::Loop(body) => {
                // Option 1.: the loop's body is `BreakableTo` the loop in question.
                let mut disj = vec![vec![Constraint::BreakableTo {
                    breaks_from: body
                        .expect("loop bodies should be nonempty after scope resolution"),
                    breaks_to,
                }]];

                // If there is a continuation, then option 2.: the loop's continuation is
                // `BreakableTo` the loop in question.
                if let Some(cont) = node.next {
                    disj.push(vec![
                        Constraint::Passable(breaks_from),
                        Constraint::BreakableTo {
                            breaks_from: cont,
                            breaks_to,
                        },
                    ]);
                }

                Dnf(disj)
            }
            // If we find a break here to the target loop, we've solved this constraint.
            Ir::Break(of_loop) if *of_loop == breaks_to => Dnf::trivially_true(),
            // Any other breaks can be ignored. `Continue` nodes may also be ignored, as they simply
            // repeat nodes we've already seen.
            Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
        }
    }
}
/// Given a [`Constraint`] and the [`Cfg`] to which it pertains, compute the preconditions which
/// would be necessary to satisfy that constraint.
Given a [`Constraint`] and the [`Cfg`] to which it pertains, compute the preconditions which
would be necessary to satisfy that constraint.
Given a [ ` Constraint ` ] and the [ ` Cfg ` ] to which it pertains compute the preconditions which would be necessary to satisfy that constraint .
fn preconditions(cfg: &Cfg, constraint: Constraint) -> Dnf {
    return match constraint {
        Constraint::Passable(node) => passable_preconditions(cfg, node),
        Constraint::Haltable(node) => haltable_preconditions(cfg, node),
        Constraint::BreakableTo {
            breaks_from,
            breaks_to,
        } => breakable_to_preconditions(cfg, breaks_from, breaks_to),
    };
    /// Compute the preconditions for a `Passable` constraint.
    fn passable_preconditions(cfg: &Cfg, node: Index) -> Dnf {
        match &cfg[node].expr {
            // Trivially, `Send`, `Recv`, and `Call(None)` nodes are passable. We also assume that
            // for any `Type` node, the external type it references is passable, and that for any
            // `Error` node, assuming it passable will give the most possible relevant errors
            // reported.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error | Ir::Call(None) => {
                Dnf::trivially_true()
            }
            // Trivially, `Break` and `Continue` nodes are never passable, because they always jump
            // and never call their continuations.
            Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
            // `Call` nodes are only passable if their continuations are haltable.
            Ir::Call(Some(callee)) => Dnf::only_if(vec![Constraint::Haltable(*callee)]),
            // Similarly to call, split nodes are only passable if both arms are haltable.
            Ir::Split { tx_only, rx_only } => {
                let arms = tx_only.iter().chain(rx_only);
                Dnf::only_if(arms.map(|&arm| Constraint::Haltable(arm)).collect())
            }
            // `Offer` and `Choose` nodes are passable if any arm is passable; however, the
            // passability of an `Offer` or `Choose` typically does not matter because as control
            // flow analysis must be run after scope resolution, they should not have an implicit
            // continuation.
            Ir::Offer(choices) | Ir::Choose(choices) => Dnf(choices
                .iter()
                .filter_map(Option::as_ref)
                .map(|&c| vec![Constraint::Passable(c)])
                .collect()),
            // The `Loop` case is the most complex case here. A `Loop` only has its continuation
            // called when a `Break` occurs targeting the loop in question, so it is only passable
            // if it is `BreakableTo` from some node inside it.
            Ir::Loop(body) => Dnf::only_if(vec![Constraint::BreakableTo {
                breaks_from: body
                    .expect("loop bodies should always be `Some` after scope resolution"),
                breaks_to: node,
            }]),
        }
    }
    /// Compute the preconditions for a `Haltable` constraint.
    fn haltable_preconditions(cfg: &Cfg, node_index: Index) -> Dnf {
        let node = &cfg[node_index];
        match &node.expr {
            // Trivially, `Send`, `Recv`, and `Type` are only haltable if they continue to a
            // haltable node (as they always continue.) We also assume `Error` behaves this way to
            // try to get the maximum possible number of reported relevant errors.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
                Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
                None => Dnf::trivially_true(),
            },
            // If a node is passable and its continuation halts, then it is always haltable. Instead
            // of spitting out constraints on the child nodes of these cases, we rely on their
            // definition of passability. Note that if the continuation is `None`, then the node
            // halts only if it is passable, so in that case a `Haltable` constraint on it is not
            // generated.
            Ir::Call(_) | Ir::Split { .. } | Ir::Loop(_) => {
                let mut conj = vec![Constraint::Passable(node_index)];
                conj.extend(node.next.map(Constraint::Haltable));
                Dnf::only_if(conj)
            }
            // `Choose`/`Offer` are haltable only if any of their choices are haltable.
            Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
                .iter()
                // If any of the choices are `Done`, we want to emit an empty `Vec` instead,
                // denoting that this constraint is trivially satisfiable.
                .map(|&c| c.map(Constraint::Haltable).into_iter().collect())
                .chain(node.next.map(|c| vec![Constraint::Haltable(c)]))
                .collect()),
            // A `Continue` only halts if whatever it jumps to halts.
            Ir::Continue(of_loop) => Dnf::only_if(vec![Constraint::Haltable(*of_loop)]),
            // Similarly, a `Break` is a kind of jump to the continuation of its relevant loop, and
            // halts only if the continuation of the loop halts.
            Ir::Break(of_loop) => match cfg[*of_loop].next {
                Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
                None => Dnf::trivially_true(),
            },
        }
    }
    /// Compute the preconditions for a `BreakableTo` constraint.
    fn breakable_to_preconditions(cfg: &Cfg, breaks_from: Index, breaks_to: Index) -> Dnf {
        let node = &cfg[breaks_from];
        // For most nodes, `BreakableTo` is inductive on its continuation.
        match &node.expr {
            // Trivially, `Send`, `Recv`, and `Type` will only break to a loop if their continuation
            // does... as obviously they do not break. Though its behavior is undefined, we assume
            // `Error` also behaves this way.
            Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
                Some(cont) => Dnf::only_if(vec![Constraint::BreakableTo {
                    breaks_from: cont,
                    breaks_to,
                }]),
                None => Dnf::trivially_false(),
            },
            // `Call` and `Split` may break from within, but that doesn't actually count as them
            // being breakable to a loop. Instead, similarly to `Send`, `Recv`, and `Type`, they are
            // `BreakableTo` only if their continuation is, but we must also know whether they are
            // passable (so that we can know if control flow can reach that break if it is present.)
            Ir::Call(_) | Ir::Split { .. } => {
                let mut conj = vec![Constraint::Passable(breaks_from)];
                conj.extend(node.next.map(|cont| Constraint::BreakableTo {
                    breaks_from: cont,
                    breaks_to,
                }));
                Dnf::only_if(conj)
            }
            // `Choose` and `Offer` break only if any arm breaks.
            Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
                .iter()
                .filter_map(Option::as_ref)
                .chain(node.next.as_ref())
                .map(|&c| {
                    vec![Constraint::BreakableTo {
                        breaks_from: c,
                        breaks_to,
                    }]
                })
                .collect()),
            // A loop only breaks to another loop in one of two cases: 1.) it contains a break
            // targeting the loop in question. 2.) it is passable, and once it is passed, its
            // continuation may break to the target.
            Ir::Loop(body) => {
                // Option 1.: the loop's body is `BreakableTo` the loop in question.
                let mut disj = vec![vec![Constraint::BreakableTo {
                    breaks_from: body
                        .expect("loop bodies should be nonempty after scope resolution"),
                    breaks_to,
                }]];
                // If there is a continuation, then option 2.: the loop's continuation is
                // `BreakableTo` the loop in question.
                if let Some(cont) = node.next {
                    disj.push(vec![
                        Constraint::Passable(breaks_from),
                        Constraint::BreakableTo {
                            breaks_from: cont,
                            breaks_to,
                        },
                    ]);
                }
                Dnf(disj)
            }
            // If we find a break here to the target loop, we've solved this constraint.
            Ir::Break(of_loop) if *of_loop == breaks_to => Dnf::trivially_true(),
            // Any other breaks can be ignored. `Continue` nodes may also be ignored, as they simply
            // repeat nodes we've already seen.
            Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
        }
    }
}
fn preconditions ( cfg : & Cfg , constraint : Constraint ) -> Dnf { return match constraint { Constraint :: Passable ( node ) => passable_preconditions ( cfg , node ) , Constraint :: Haltable ( node ) => haltable_preconditions ( cfg , node ) , Constraint :: BreakableTo { breaks_from , breaks_to , } => breakable_to_preconditions ( cfg , breaks_from , breaks_to ) , } ; /// Compute the preconditions for a `Passable` constraint. fn passable_preconditions ( cfg : & Cfg , node : Index ) -> Dnf { match & cfg [ node ] . expr { // Trivially, `Send`, `Recv`, and `Call(None)` nodes are passable. We also assume that // for any `Type` node, the external type it references is passable, and that for any // `Error` node, assuming it passable will give the most possible relevant errors // reported. Ir :: Send ( _ ) | Ir :: Recv ( _ ) | Ir :: Type ( _ ) | Ir :: Error | Ir :: Call ( None ) => { Dnf :: trivially_true ( ) } // Trivially, `Break` and `Continue` nodes are never passable, because they always jump // and never call their continuations. Ir :: Break ( _ ) | Ir :: Continue ( _ ) => Dnf :: trivially_false ( ) , // `Call` nodes are only passable if their continuations are haltable. Ir :: Call ( Some ( callee ) ) => Dnf :: only_if ( vec ! [ Constraint Haltable ( callee ) ] ) , // Similarly to call, split nodes are only passable if both arms are haltable. Ir :: Split { tx_only , rx_only } => { let arms = tx_only . iter ( ) . chain ( rx_only ) ; Dnf :: only_if ( arms . map ( | & arm | Constraint :: Haltable ( arm ) ) . collect ( ) ) } // `Offer` and `Choose` nodes are passable if any arm is passable; however, the // passability of an `Offer` or `Choose` typically does not matter because as control // flow analysis must be run after scope resolution, they should not have an implicit // continuation. Ir :: Offer ( choices ) | Ir :: Choose ( choices ) => Dnf ( choices . iter ( ) . filter_map ( Option :: as_ref ) . map ( | & c | vec ! [ Constraint Passable ( c ) ] ) . collect ( ) ) , // The `Loop` case is the most complex case here. A `Loop` only has its continuation // called when a `Break` occurs targeting the loop in question, so it is only passable // if it is `BreakableTo` from some node inside it. Ir :: Loop ( body ) => Dnf :: only_if ( vec ! [ Constraint BreakableTo { breaks_from body expect ( " " ) breaks_to node } ] ) , } } /// Compute the preconditions for a `Haltable` constraint. fn haltable_preconditions ( cfg : & Cfg , node_index : Index ) -> Dnf { let node = & cfg [ node_index ] ; match & node . expr { // Trivially, `Send`, `Recv`, and `Type` are only haltable if they continue to a // haltable node (as they always continue.) We also assume `Error` behaves this way to // try to get the maximum possible number of reported relevant errors. Ir :: Send ( _ ) | Ir :: Recv ( _ ) | Ir :: Type ( _ ) | Ir :: Error => match node . next { Some ( cont ) => Dnf :: only_if ( vec ! [ Constraint Haltable ( cont ) ] ) , None => Dnf :: trivially_true ( ) , } , // If a node is passable and its continuation halts, then it is always haltable. Instead // of spitting out constraints on the child nodes of these cases, we rely on their // definition of passability. Note that if the continuation is `None`, then the node // halts only if it is passable, so in that case a `Haltable` constraint on it is not // generated. Ir :: Call ( _ ) | Ir :: Split { .. } | Ir :: Loop ( _ ) => { let mut conj = vec ! [ Constraint Passable ( node_index ) ] ; conj . extend ( node . next . map ( Constraint :: Haltable ) ) ; Dnf :: only_if ( conj ) } // `Choose`/`Offer` are haltable only if any of their choices are haltable. Ir :: Choose ( choices ) | Ir :: Offer ( choices ) => Dnf ( choices . iter ( ) // If any of the choices are `Done`, we want to emit an empty `Vec` instead, // denoting that this constraint is trivially satisfiable. . map ( | & c | c . map ( Constraint :: Haltable ) . into_iter ( ) . collect ( ) ) . chain ( node . next . map ( | c | vec ! [ Constraint Haltable ( c ) ] ) ) . collect ( ) ) , // A `Continue` only halts if whatever it jumps to halts. Ir :: Continue ( of_loop ) => Dnf :: only_if ( vec ! [ Constraint Haltable ( of_loop ) ] ) , // Similarly, a `Break` is a kind of jump to the continuation of its relevant loop, and // halts only if the continuation of the loop halts. Ir :: Break ( of_loop ) => match cfg [ * of_loop ] . next { Some ( cont ) => Dnf :: only_if ( vec ! [ Constraint Haltable ( cont ) ] ) , None => Dnf :: trivially_true ( ) , } , } } /// Compute the preconditions for a `BreakableTo` constraint. fn breakable_to_preconditions ( cfg : & Cfg , breaks_from : Index , breaks_to : Index ) -> Dnf { let node = & cfg [ breaks_from ] ; // For most nodes, `BreakableTo` is inductive on its continuation. match & node . expr { // Trivially, `Send`, `Recv`, and `Type` will only break to a loop if their continuation // does... as obviously they do not break. Though its behavior is undefined, we assume // `Error` also behaves this way. Ir :: Send ( _ ) | Ir :: Recv ( _ ) | Ir :: Type ( _ ) | Ir :: Error => match node . next { Some ( cont ) => Dnf :: only_if ( vec ! [ Constraint BreakableTo { breaks_from cont breaks_to } ] ) , None => Dnf :: trivially_false ( ) , } , // `Call` and `Split` may break from within, but that doesn't actually count as them // being breakable to a loop. Instead, similarly to `Send`, `Recv`, and `Type`, they are // `BreakableTo` only if their continuation is, but we must also know whether they are // passable (so that we can know if control flow can reach that break if it is present.) Ir :: Call ( _ ) | Ir :: Split { .. } => { let mut conj = vec ! [ Constraint Passable ( breaks_from ) ] ; conj . extend ( node . next . map ( | cont | Constraint :: BreakableTo { breaks_from : cont , breaks_to , } ) ) ; Dnf :: only_if ( conj ) } // `Choose` and `Offer` break only if any arm breaks. Ir :: Choose ( choices ) | Ir :: Offer ( choices ) => Dnf ( choices . iter ( ) . filter_map ( Option :: as_ref ) . chain ( node . next . as_ref ( ) ) . map ( | & c | { vec ! [ Constraint BreakableTo { breaks_from c breaks_to } ] } ) . collect ( ) ) , // A loop only breaks to another loop in one of two cases: 1.) it contains a break // targeting the loop in question. 2.) it is passable, and once it is passed, its // continuation may break to the target. Ir :: Loop ( body ) => { // Option 1.: the loop's body is `BreakableTo` the loop in question. let mut disj = vec ! [ vec [ Constraint BreakableTo { breaks_from body expect ( " " ) breaks_to } ] ] ; // If there is a continuation, then option 2.: the loop's continuation is // `BreakableTo` the loop in question. if let Some ( cont ) = node . next { disj . push ( vec ! [ Constraint Passable ( breaks_from ) Constraint BreakableTo { breaks_from cont breaks_to } ] ) ; } Dnf ( disj ) } // If we find a break here to the target loop, we've solved this constraint. Ir :: Break ( of_loop ) if * of_loop == breaks_to => Dnf :: trivially_true ( ) , // Any other breaks can be ignored. `Continue` nodes may also be ignored, as they simply // repeat nodes we've already seen. Ir :: Break ( _ ) | Ir :: Continue ( _ ) => Dnf :: trivially_false ( ) , } } }
Given a [`Constraint`] and the [`Cfg`] to which it pertains, compute the preconditions which
would be necessary to satisfy that constraint.
Given a [ ` Constraint ` ] and the [ ` Cfg ` ] to which it pertains compute the preconditions which would be necessary to satisfy that constraint .
cfg
&Cfg
constraint
Constraint