c2rust-transpile 0.22.1

C2Rust transpiler implementation
Documentation
//! This module provides support for removing the extraneous break statements
//! generated by the incremental relooper.
use super::*;
use syn::{Expr, ExprBlock, ExprBreak, ExprIf, ExprLit, ExprMatch, ExprReturn, Lit};

pub struct IncCleanup {
    in_tail: Option<ImplicitReturnType>,
    brk_lbl: Label,
}

impl IncCleanup {
    pub fn new(in_tail: Option<ImplicitReturnType>, brk_lbl: Label) -> Self {
        IncCleanup { in_tail, brk_lbl }
    }

    /// The only way we can say for sure that we don't need a labelled block is if we remove
    /// the (unique) break to that label. We know that the label will be unique because relooper
    /// never duplicates blocks.
    ///
    /// Returns true if we manage to remove a tail expr.
    pub fn remove_tail_expr(&self, stmts: &mut Vec<Stmt>) -> bool {
        let mut stmt = if let Some(stmt) = stmts.pop() {
            stmt
        } else {
            return false;
        };
        // If the very last stmt in our relooped output is a return/break, we can just
        // remove that statement. We additionally know that there is definitely no need
        // to label a block (if we were in that mode in the first place).
        if self.is_idempotent_tail_expr(&stmt) {
            return true;
        }

        let mut removed_tail_expr = false;

        if let Stmt::Expr(expr, None) = &mut stmt {
            match expr {
                Expr::If(ExprIf {
                    cond: _,
                    then_branch,
                    else_branch,
                    ..
                }) => {
                    removed_tail_expr =
                        removed_tail_expr || self.remove_tail_expr(&mut then_branch.stmts);
                    if let Some((_token, else_)) = else_branch {
                        if let Expr::Block(ExprBlock { block, .. }) = &mut **else_ {
                            removed_tail_expr =
                                removed_tail_expr || self.remove_tail_expr(&mut block.stmts)
                        }
                    }
                }

                Expr::Match(ExprMatch { arms, .. }) => {
                    // Block label can be removed from any arm
                    for arm in arms {
                        if let Expr::Block(ExprBlock { block, .. }) = arm.body.as_mut() {
                            removed_tail_expr =
                                removed_tail_expr || self.remove_tail_expr(&mut block.stmts);
                        }
                    }
                }

                _ => {}
            }
        }

        stmt = cleanup_if(stmt);

        // In all other cases, we give up and accept that we can't get rid of the last
        // stmt and that we might need a block label.
        stmts.push(stmt);
        removed_tail_expr
    }

    fn is_idempotent_tail_expr(&self, stmt: &Stmt) -> bool {
        let tail_expr = if let Stmt::Expr(expr, Some(_token)) = stmt {
            expr
        } else {
            return false;
        };
        match self.in_tail {
            Some(ImplicitReturnType::Main) => {
                if let Expr::Return(ExprReturn {
                    expr: Some(zero), ..
                }) = tail_expr
                {
                    if let Expr::Lit(ExprLit {
                        lit: Lit::Int(lit), ..
                    }) = &**zero
                    {
                        if lit.base10_digits() == "0" {
                            return true;
                        }
                    }
                }
                false
            }

            Some(ImplicitReturnType::Void) => {
                if let Expr::Return(ExprReturn { expr: None, .. }) = tail_expr {
                    return true;
                }
                false
            }

            _ => {
                if let Expr::Break(ExprBreak {
                    label: Some(brk_lbl),
                    expr: None,
                    ..
                }) = tail_expr
                {
                    if brk_lbl.ident == mk().label(self.brk_lbl.pretty_print()).name.ident {
                        return true;
                    }
                }
                false
            }
        }
    }
}

/// Remove empty else clauses from if expressions that can arise from
/// removing idempotent statements.
fn cleanup_if(stmt: Stmt) -> Stmt {
    if let Stmt::Expr(
        Expr::If(ExprIf {
            cond,
            then_branch,
            else_branch: Some((token, else_)),
            ..
        }),
        None,
    ) = &stmt
    {
        if let Expr::Block(ExprBlock {
            block, label: None, ..
        }) = &**else_
        {
            if block.stmts.is_empty() {
                return Stmt::Expr(
                    Expr::If(ExprIf {
                        cond: cond.clone(),
                        then_branch: then_branch.clone(),
                        else_branch: Default::default(),
                        attrs: Default::default(),
                        if_token: Default::default(),
                    }),
                    None,
                );
            } else if block.stmts.len() == 1 {
                // flatten nested if expression to else if chain
                if let Stmt::Expr(Expr::If(nested_if_expr), None) = &block.stmts[0] {
                    return Stmt::Expr(
                        Expr::If(ExprIf {
                            cond: cond.clone(),
                            then_branch: then_branch.clone(),
                            else_branch: Some((*token, Box::new(Expr::If(nested_if_expr.clone())))),
                            attrs: Default::default(),
                            if_token: Default::default(),
                        }),
                        None,
                    );
                }
            }
        }
    }
    stmt
}