leo_passes/
remove_unreachable.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::{CompilerState, Pass};
18
19use leo_ast::*;
20use leo_errors::Result;
21
22pub struct RemoveUnreachableOutput {
23    /// Something about the program was actually changed during the pass.
24    pub changed: bool,
25}
26
27/// Pass that removes unreachable code created by early returns
28pub struct RemoveUnreachable;
29
30impl Pass for RemoveUnreachable {
31    type Input = ();
32    type Output = RemoveUnreachableOutput;
33
34    const NAME: &str = "RemoveUnreachable";
35
36    fn do_pass(_input: Self::Input, state: &mut crate::CompilerState) -> Result<Self::Output> {
37        let mut ast = std::mem::take(&mut state.ast);
38        let mut visitor = RemoveUnreachableVisitor { changed: false, state, has_return: false };
39        ast.ast = visitor.reconstruct_program(ast.ast);
40        visitor.state.ast = ast;
41        Ok(RemoveUnreachableOutput { changed: visitor.changed })
42    }
43}
44
45pub struct RemoveUnreachableVisitor<'state> {
46    pub state: &'state mut CompilerState,
47    /// Have we actually modified the program at all?
48    pub changed: bool,
49    /// Have we already returned in the current scope?
50    pub has_return: bool,
51}
52
53impl ProgramReconstructor for RemoveUnreachableVisitor<'_> {
54    fn reconstruct_function(&mut self, input: Function) -> Function {
55        self.has_return = false;
56        let res = Function {
57            annotations: input.annotations,
58            variant: input.variant,
59            identifier: input.identifier,
60            const_parameters: input
61                .const_parameters
62                .iter()
63                .map(|param| ConstParameter { type_: self.reconstruct_type(param.type_.clone()).0, ..param.clone() })
64                .collect(),
65            input: input
66                .input
67                .iter()
68                .map(|input| Input { type_: self.reconstruct_type(input.type_.clone()).0, ..input.clone() })
69                .collect(),
70            output: input
71                .output
72                .iter()
73                .map(|output| Output { type_: self.reconstruct_type(output.type_.clone()).0, ..output.clone() })
74                .collect(),
75            output_type: self.reconstruct_type(input.output_type).0,
76            block: self.reconstruct_block(input.block).0,
77            span: input.span,
78            id: input.id,
79        };
80        self.has_return = false;
81        res
82    }
83
84    fn reconstruct_constructor(&mut self, input: Constructor) -> Constructor {
85        self.has_return = false;
86        let res = Constructor {
87            annotations: input.annotations,
88            block: self.reconstruct_block(input.block).0,
89            span: input.span,
90            id: input.id,
91        };
92        self.has_return = false;
93        res
94    }
95}
96
97impl AstReconstructor for RemoveUnreachableVisitor<'_> {
98    type AdditionalInput = ();
99    type AdditionalOutput = ();
100
101    fn reconstruct_block(&mut self, input: Block) -> (Block, Self::AdditionalOutput) {
102        // Produce every reconstructed statement until you see an unconditional return
103        // including the return itself
104        let statements_with_first_return_only = input
105            .statements
106            .into_iter()
107            .scan(false, |return_seen, s| {
108                let stmt = self.reconstruct_statement(s).0;
109                let res = (!*return_seen).then_some(stmt);
110                *return_seen |= self.has_return;
111                res
112            })
113            .filter_map(Some)
114            .collect();
115        (Block { statements: statements_with_first_return_only, span: input.span, id: input.id }, Default::default())
116    }
117
118    fn reconstruct_conditional(&mut self, input: ConditionalStatement) -> (Statement, Self::AdditionalOutput) {
119        // Conditionals have their own scopes with respect to seeing returns because they are not always executed
120        // We save the current has_return value and run the conditional scopes independently
121        let mut then_block_has_return = false;
122        let mut otherwise_block_has_return = false;
123        let previous_has_return = core::mem::replace(&mut self.has_return, then_block_has_return);
124
125        let then = self.reconstruct_block(input.then).0;
126        then_block_has_return = self.has_return;
127
128        let otherwise = input.otherwise.map(|otherwise| {
129            self.has_return = otherwise_block_has_return;
130            let res = Box::new(self.reconstruct_statement(*otherwise).0);
131            otherwise_block_has_return = self.has_return;
132            res
133        });
134
135        // Either we already had returned or return is certain because both branches have returned
136        self.has_return = previous_has_return || (then_block_has_return && otherwise_block_has_return);
137
138        (
139            ConditionalStatement {
140                condition: self.reconstruct_expression(input.condition, &Default::default()).0,
141                then,
142                otherwise,
143                ..input
144            }
145            .into(),
146            Default::default(),
147        )
148    }
149
150    fn reconstruct_iteration(&mut self, input: IterationStatement) -> (Statement, Self::AdditionalOutput) {
151        let prior_has_return = core::mem::take(&mut self.has_return);
152        let block = self.reconstruct_block(input.block).0;
153        self.has_return = prior_has_return;
154
155        (
156            IterationStatement {
157                type_: input.type_.map(|ty| self.reconstruct_type(ty).0),
158                start: self.reconstruct_expression(input.start, &Default::default()).0,
159                stop: self.reconstruct_expression(input.stop, &Default::default()).0,
160                block,
161                ..input
162            }
163            .into(),
164            Default::default(),
165        )
166    }
167
168    fn reconstruct_return(&mut self, input: ReturnStatement) -> (Statement, Self::AdditionalOutput) {
169        self.has_return = true;
170        (
171            ReturnStatement {
172                expression: self.reconstruct_expression(input.expression, &Default::default()).0,
173                ..input
174            }
175            .into(),
176            Default::default(),
177        )
178    }
179}