leo_passes/static_analysis/
visitor.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, ConditionalTreeNode, static_analysis::await_checker::AwaitChecker};
18
19use leo_ast::*;
20use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning};
21use leo_span::{Span, Symbol};
22
23pub struct StaticAnalyzingVisitor<'a> {
24    pub state: &'a mut CompilerState,
25    /// Struct to store the state relevant to checking all futures are awaited.
26    pub await_checker: AwaitChecker,
27    /// The current program name.
28    pub current_program: Symbol,
29    /// The variant of the function that we are currently traversing.
30    pub variant: Option<Variant>,
31    /// Whether or not a non-async external call has been seen in this function.
32    pub non_async_external_call_seen: bool,
33}
34
35impl StaticAnalyzingVisitor<'_> {
36    pub fn emit_err(&self, err: StaticAnalyzerError) {
37        self.state.handler.emit_err(err);
38    }
39
40    /// Emits a type checker warning
41    pub fn emit_warning(&self, warning: StaticAnalyzerWarning) {
42        self.state.handler.emit_warning(warning.into());
43    }
44
45    /// Type checks the awaiting of a future.
46    pub fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) {
47        // Make sure that it is an identifier expression.
48        let future_variable = match future {
49            Some(Expression::Path(path)) => path,
50            _ => {
51                return self.emit_err(StaticAnalyzerError::invalid_await_call(span));
52            }
53        };
54
55        // Make sure that the future is defined.
56        match self.state.type_table.get(&future_variable.id) {
57            Some(type_) => {
58                if !matches!(type_, Type::Future(_)) {
59                    self.emit_err(StaticAnalyzerError::expected_future(type_, future_variable.span()));
60                }
61                // Mark the future as consumed.
62                // If the call returns true, it means that a future was not awaited in the order of the input list, emit a warning.
63                if self.await_checker.remove(&future_variable.identifier().name) {
64                    self.emit_warning(StaticAnalyzerWarning::future_not_awaited_in_order(
65                        future_variable,
66                        future_variable.span(),
67                    ));
68                }
69            }
70            None => {
71                self.emit_err(StaticAnalyzerError::expected_future(future_variable, future_variable.span()));
72            }
73        }
74    }
75
76    /// Assert that an async call is a "simple" one.
77    /// Simple is defined as an async transition function which does not return a `Future` that itself takes a `Future` as an argument.
78    pub fn assert_simple_async_transition_call(&mut self, program: Symbol, function_path: &Path, span: Span) {
79        let func_symbol = self
80            .state
81            .symbol_table
82            .lookup_function(&Location::new(program, function_path.absolute_path().to_vec()))
83            .expect("Type checking guarantees functions are present.");
84
85        // If it is not an async transition, return.
86        if func_symbol.function.variant != Variant::AsyncTransition {
87            return;
88        }
89
90        let finalizer = func_symbol
91            .finalizer
92            .as_ref()
93            .expect("Typechecking guarantees that all async transitions have an associated `finalize` field.");
94
95        let async_function = self
96            .state
97            .symbol_table
98            .lookup_function(&finalizer.location)
99            .expect("Type checking guarantees functions are present.");
100
101        // If the async function takes a future as an argument, emit an error.
102        if async_function.function.input.iter().any(|input| matches!(input.type_(), Type::Future(..))) {
103            self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_path, span));
104        }
105    }
106}
107
108impl AstVisitor for StaticAnalyzingVisitor<'_> {
109    /* Expressions */
110    type AdditionalInput = ();
111    type Output = ();
112
113    fn visit_associated_function(
114        &mut self,
115        input: &AssociatedFunctionExpression,
116        _additional: &Self::AdditionalInput,
117    ) -> Self::Output {
118        // Get the core function.
119        let Some(core_function) = CoreFunction::from_symbols(input.variant.name, input.name.name) else {
120            panic!("Typechecking guarantees that this function exists.");
121        };
122
123        // Check that the future was awaited correctly.
124        if core_function == CoreFunction::FutureAwait {
125            self.assert_future_await(&input.arguments.first(), input.span());
126        }
127    }
128
129    fn visit_call(&mut self, input: &CallExpression, _: &Self::AdditionalInput) -> Self::Output {
130        let caller_program = self.current_program;
131        let callee_program = input.program.unwrap_or(caller_program);
132
133        // If the function call is an external async transition, then for all async calls that follow a non-async call,
134        // we must check that the async call is not an async function that takes a future as an argument.
135        if self.non_async_external_call_seen
136            && self.variant == Some(Variant::AsyncTransition)
137            && callee_program != caller_program
138        {
139            self.assert_simple_async_transition_call(callee_program, &input.function, input.span());
140        }
141
142        // Look up the function and check if it is a non-async call.
143        let function_program = input.program.unwrap_or(self.current_program);
144
145        let func_symbol = self
146            .state
147            .symbol_table
148            .lookup_function(&Location::new(function_program, input.function.absolute_path().to_vec()))
149            .expect("Type checking guarantees functions exist.");
150
151        if func_symbol.function.variant == Variant::Transition {
152            self.non_async_external_call_seen = true;
153        }
154    }
155
156    /* Statements */
157    fn visit_conditional(&mut self, input: &ConditionalStatement) {
158        self.visit_expression(&input.condition, &Default::default());
159
160        // Create scope for checking awaits in `then` branch of conditional.
161        let current_bst_nodes: Vec<ConditionalTreeNode> =
162            match self.await_checker.create_then_scope(self.variant == Some(Variant::AsyncFunction), input.span) {
163                Ok(nodes) => nodes,
164                Err(warn) => return self.emit_warning(warn),
165            };
166
167        // Visit block.
168        self.visit_block(&input.then);
169
170        // Exit scope for checking awaits in `then` branch of conditional.
171        let saved_paths =
172            self.await_checker.exit_then_scope(self.variant == Some(Variant::AsyncFunction), current_bst_nodes);
173
174        if let Some(otherwise) = &input.otherwise {
175            match &**otherwise {
176                Statement::Block(stmt) => {
177                    // Visit the otherwise-block.
178                    self.visit_block(stmt);
179                }
180                Statement::Conditional(stmt) => self.visit_conditional(stmt),
181                _ => unreachable!("Else-case can only be a block or conditional statement."),
182            }
183        }
184
185        // Update the set of all possible BST paths.
186        self.await_checker.exit_statement_scope(self.variant == Some(Variant::AsyncFunction), saved_paths);
187    }
188}