Skip to main content

leo_passes/
remove_unreachable.rs

1// Copyright (C) 2019-2026 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 ast = std::mem::take(&mut state.ast);
38        let mut visitor = RemoveUnreachableVisitor { changed: false, state, has_return: false };
39
40        let ast = ast.map(
41            |program| visitor.reconstruct_program(program),
42            |library| library, // no-op for libraries
43        );
44
45        visitor.state.ast = ast;
46        Ok(RemoveUnreachableOutput { changed: visitor.changed })
47    }
48}
49
50pub struct RemoveUnreachableVisitor<'state> {
51    pub state: &'state mut CompilerState,
52    /// Have we actually modified the program at all?
53    pub changed: bool,
54    /// Have we already returned in the current scope?
55    pub has_return: bool,
56}
57
58impl ProgramReconstructor for RemoveUnreachableVisitor<'_> {
59    fn reconstruct_function(&mut self, input: Function) -> Function {
60        self.has_return = false;
61        let res = Function {
62            annotations: input.annotations,
63            variant: input.variant,
64            identifier: input.identifier,
65            const_parameters: input
66                .const_parameters
67                .iter()
68                .map(|param| ConstParameter { type_: self.reconstruct_type(param.type_.clone()).0, ..param.clone() })
69                .collect(),
70            input: input
71                .input
72                .iter()
73                .map(|input| Input { type_: self.reconstruct_type(input.type_.clone()).0, ..input.clone() })
74                .collect(),
75            output: input
76                .output
77                .iter()
78                .map(|output| Output { type_: self.reconstruct_type(output.type_.clone()).0, ..output.clone() })
79                .collect(),
80            output_type: self.reconstruct_type(input.output_type).0,
81            block: self.reconstruct_block(input.block).0,
82            span: input.span,
83            id: input.id,
84        };
85        self.has_return = false;
86        res
87    }
88
89    fn reconstruct_constructor(&mut self, input: Constructor) -> Constructor {
90        self.has_return = false;
91        let res = Constructor {
92            annotations: input.annotations,
93            block: self.reconstruct_block(input.block).0,
94            span: input.span,
95            id: input.id,
96        };
97        self.has_return = false;
98        res
99    }
100}
101
102impl AstReconstructor for RemoveUnreachableVisitor<'_> {
103    type AdditionalInput = ();
104    type AdditionalOutput = ();
105
106    fn reconstruct_block(&mut self, input: Block) -> (Block, Self::AdditionalOutput) {
107        // Produce every reconstructed statement until you see an unconditional return
108        // including the return itself
109        let statements_with_first_return_only = input
110            .statements
111            .into_iter()
112            .scan(false, |return_seen, s| {
113                let stmt = self.reconstruct_statement(s).0;
114                let res = (!*return_seen).then_some(stmt);
115                *return_seen |= self.has_return;
116                res
117            })
118            .collect();
119        (Block { statements: statements_with_first_return_only, span: input.span, id: input.id }, Default::default())
120    }
121
122    fn reconstruct_conditional(&mut self, input: ConditionalStatement) -> (Statement, Self::AdditionalOutput) {
123        // Conditionals have their own scopes with respect to seeing returns because they are not always executed
124        // We save the current has_return value and run the conditional scopes independently
125        let mut then_block_has_return = false;
126        let mut otherwise_block_has_return = false;
127        let previous_has_return = core::mem::replace(&mut self.has_return, then_block_has_return);
128
129        let then = self.reconstruct_block(input.then).0;
130        then_block_has_return = self.has_return;
131
132        let otherwise = input.otherwise.map(|otherwise| {
133            self.has_return = otherwise_block_has_return;
134            let res = Box::new(self.reconstruct_statement(*otherwise).0);
135            otherwise_block_has_return = self.has_return;
136            res
137        });
138
139        // Either we already had returned or return is certain because both branches have returned
140        self.has_return = previous_has_return || (then_block_has_return && otherwise_block_has_return);
141
142        (
143            ConditionalStatement {
144                condition: self.reconstruct_expression(input.condition, &Default::default()).0,
145                then,
146                otherwise,
147                ..input
148            }
149            .into(),
150            Default::default(),
151        )
152    }
153
154    fn reconstruct_iteration(&mut self, input: IterationStatement) -> (Statement, Self::AdditionalOutput) {
155        let prior_has_return = core::mem::take(&mut self.has_return);
156        let block = self.reconstruct_block(input.block).0;
157        self.has_return = prior_has_return;
158
159        (
160            IterationStatement {
161                type_: input.type_.map(|ty| self.reconstruct_type(ty).0),
162                start: self.reconstruct_expression(input.start, &Default::default()).0,
163                stop: self.reconstruct_expression(input.stop, &Default::default()).0,
164                block,
165                ..input
166            }
167            .into(),
168            Default::default(),
169        )
170    }
171
172    fn reconstruct_return(&mut self, input: ReturnStatement) -> (Statement, Self::AdditionalOutput) {
173        self.has_return = true;
174        (
175            ReturnStatement {
176                expression: self.reconstruct_expression(input.expression, &Default::default()).0,
177                ..input
178            }
179            .into(),
180            Default::default(),
181        )
182    }
183}