leo_passes/static_analysis/
future_checker.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::TypeTable;
18
19use leo_ast::{CoreFunction, Expression, ExpressionVisitor, Function, Node, StatementVisitor, Type, TypeVisitor};
20use leo_errors::{Handler, StaticAnalyzerError};
21
22/// Error if futures are used improperly.
23///
24/// This prevents, for instance, a bare call which creates an unused future.
25pub fn future_check_function(function: &Function, type_table: &TypeTable, handler: &Handler) {
26    let mut future_checker = FutureChecker { type_table, handler };
27    future_checker.visit_block(&function.block);
28}
29
30#[derive(Clone, Copy, Debug, Default)]
31enum Position {
32    #[default]
33    Misc,
34    Await,
35    TupleAccess,
36    Return,
37    FunctionArgument,
38    LastTupleLiteral,
39    Definition,
40}
41
42struct FutureChecker<'a> {
43    type_table: &'a TypeTable,
44    handler: &'a Handler,
45}
46
47impl FutureChecker<'_> {
48    fn emit_err(&self, err: StaticAnalyzerError) {
49        self.handler.emit_err(err);
50    }
51}
52
53impl TypeVisitor for FutureChecker<'_> {}
54
55impl ExpressionVisitor for FutureChecker<'_> {
56    type AdditionalInput = Position;
57    type Output = ();
58
59    fn visit_expression(&mut self, input: &Expression, additional: &Self::AdditionalInput) -> Self::Output {
60        use Position::*;
61        let is_call = matches!(input, Expression::Call(..));
62        match self.type_table.get(&input.id()) {
63            Some(Type::Future(..)) if is_call => {
64                // A call producing a Future may appear in any of these positions.
65                if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | Definition) {
66                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
67                }
68            }
69            Some(Type::Future(..)) => {
70                // A Future expression that's not a call may appear in any of these positions.
71                if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | TupleAccess) {
72                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
73                }
74            }
75            Some(Type::Tuple(tuple)) if !matches!(tuple.elements().last(), Some(Type::Future(_))) => {}
76            Some(Type::Tuple(..)) if is_call => {
77                // A call producing a Tuple ending in a Future may appear in any of these positions.
78                if !matches!(additional, Return | Definition) {
79                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
80                }
81            }
82            Some(Type::Tuple(..)) => {
83                // A Tuple ending in a Future that's not a call may appear in any of these positions.
84                if !matches!(additional, Return | TupleAccess) {
85                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
86                }
87            }
88            _ => {}
89        }
90
91        match input {
92            Expression::Array(array) => self.visit_array(array, &Position::Misc),
93            Expression::ArrayAccess(access) => self.visit_array_access(access, &Position::Misc),
94            Expression::AssociatedConstant(constant) => self.visit_associated_constant(constant, &Position::Misc),
95            Expression::AssociatedFunction(function) => self.visit_associated_function(function, &Position::Misc),
96            Expression::Binary(binary) => self.visit_binary(binary, &Position::Misc),
97            Expression::Call(call) => self.visit_call(call, &Position::Misc),
98            Expression::Cast(cast) => self.visit_cast(cast, &Position::Misc),
99            Expression::Struct(struct_) => self.visit_struct_init(struct_, &Position::Misc),
100            Expression::Err(err) => self.visit_err(err, &Position::Misc),
101            Expression::Identifier(identifier) => self.visit_identifier(identifier, &Position::Misc),
102            Expression::Literal(literal) => self.visit_literal(literal, &Position::Misc),
103            Expression::Locator(locator) => self.visit_locator(locator, &Position::Misc),
104            Expression::MemberAccess(access) => self.visit_member_access(access, &Position::Misc),
105            Expression::Repeat(repeat) => self.visit_repeat(repeat, &Position::Misc),
106            Expression::Ternary(ternary) => self.visit_ternary(ternary, &Position::Misc),
107            Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
108            Expression::TupleAccess(access) => self.visit_tuple_access(access, &Position::Misc),
109            Expression::Unary(unary) => self.visit_unary(unary, &Position::Misc),
110            Expression::Unit(unit) => self.visit_unit(unit, &Position::Misc),
111        }
112    }
113
114    fn visit_array_access(
115        &mut self,
116        input: &leo_ast::ArrayAccess,
117        _additional: &Self::AdditionalInput,
118    ) -> Self::Output {
119        self.visit_expression(&input.array, &Position::Misc);
120        self.visit_expression(&input.index, &Position::Misc);
121    }
122
123    fn visit_member_access(
124        &mut self,
125        input: &leo_ast::MemberAccess,
126        _additional: &Self::AdditionalInput,
127    ) -> Self::Output {
128        self.visit_expression(&input.inner, &Position::Misc);
129    }
130
131    fn visit_tuple_access(
132        &mut self,
133        input: &leo_ast::TupleAccess,
134        _additional: &Self::AdditionalInput,
135    ) -> Self::Output {
136        self.visit_expression(&input.tuple, &Position::TupleAccess);
137    }
138
139    fn visit_associated_function(
140        &mut self,
141        input: &leo_ast::AssociatedFunctionExpression,
142        _additional: &Self::AdditionalInput,
143    ) -> Self::Output {
144        let core_function = CoreFunction::from_symbols(input.variant.name, input.name.name)
145            .expect("Typechecking guarantees that this function exists.");
146        let position = if core_function == CoreFunction::FutureAwait { Position::Await } else { Position::Misc };
147        input.arguments.iter().for_each(|arg| {
148            self.visit_expression(arg, &position);
149        });
150    }
151
152    fn visit_call(&mut self, input: &leo_ast::CallExpression, _additional: &Self::AdditionalInput) -> Self::Output {
153        input.arguments.iter().for_each(|expr| {
154            self.visit_expression(expr, &Position::FunctionArgument);
155        });
156        Default::default()
157    }
158
159    fn visit_tuple(&mut self, input: &leo_ast::TupleExpression, additional: &Self::AdditionalInput) -> Self::Output {
160        let next_position = match additional {
161            Position::Definition | Position::Return => Position::LastTupleLiteral,
162            _ => Position::Misc,
163        };
164        let mut iter = input.elements.iter().peekable();
165        while let Some(expr) = iter.next() {
166            let position = if iter.peek().is_some() { &Position::Misc } else { &next_position };
167            self.visit_expression(expr, position);
168        }
169        Default::default()
170    }
171}
172
173impl StatementVisitor for FutureChecker<'_> {
174    fn visit_definition(&mut self, input: &leo_ast::DefinitionStatement) {
175        if let Some(ty) = input.type_.as_ref() {
176            self.visit_type(ty)
177        }
178        self.visit_expression(&input.value, &Position::Definition);
179    }
180
181    fn visit_return(&mut self, input: &leo_ast::ReturnStatement) {
182        self.visit_expression(&input.expression, &Position::Return);
183    }
184}