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::{AstVisitor, CoreFunction, Expression, Function, Node, Type};
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 AstVisitor for FutureChecker<'_> {
54    /* Expressions */
55    type AdditionalInput = Position;
56    type Output = ();
57
58    fn visit_expression(&mut self, input: &Expression, additional: &Self::AdditionalInput) -> Self::Output {
59        use Position::*;
60        let is_call = matches!(input, Expression::Call(..));
61        match self.type_table.get(&input.id()) {
62            Some(Type::Future(..)) if is_call => {
63                // A call producing a Future may appear in any of these positions.
64                if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | Definition) {
65                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
66                }
67            }
68            Some(Type::Future(..)) => {
69                // A Future expression that's not a call may appear in any of these positions.
70                if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | TupleAccess) {
71                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
72                }
73            }
74            Some(Type::Tuple(tuple)) if !matches!(tuple.elements().last(), Some(Type::Future(_))) => {}
75            Some(Type::Tuple(..)) if is_call => {
76                // A call producing a Tuple ending in a Future may appear in any of these positions.
77                if !matches!(additional, Return | Definition) {
78                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
79                }
80            }
81            Some(Type::Tuple(..)) => {
82                // A Tuple ending in a Future that's not a call may appear in any of these positions.
83                if !matches!(additional, Return | TupleAccess) {
84                    self.emit_err(StaticAnalyzerError::misplaced_future(input.span()));
85                }
86            }
87            _ => {}
88        }
89
90        match input {
91            Expression::Array(array) => self.visit_array(array, &Position::Misc),
92            Expression::ArrayAccess(access) => self.visit_array_access(access, &Position::Misc),
93            Expression::AssociatedConstant(constant) => self.visit_associated_constant(constant, &Position::Misc),
94            Expression::AssociatedFunction(function) => self.visit_associated_function(function, &Position::Misc),
95            Expression::Binary(binary) => self.visit_binary(binary, &Position::Misc),
96            Expression::Call(call) => self.visit_call(call, &Position::Misc),
97            Expression::Cast(cast) => self.visit_cast(cast, &Position::Misc),
98            Expression::Struct(struct_) => self.visit_struct_init(struct_, &Position::Misc),
99            Expression::Err(err) => self.visit_err(err, &Position::Misc),
100            Expression::Identifier(identifier) => self.visit_identifier(identifier, &Position::Misc),
101            Expression::Literal(literal) => self.visit_literal(literal, &Position::Misc),
102            Expression::Locator(locator) => self.visit_locator(locator, &Position::Misc),
103            Expression::MemberAccess(access) => self.visit_member_access(access, &Position::Misc),
104            Expression::Repeat(repeat) => self.visit_repeat(repeat, &Position::Misc),
105            Expression::Ternary(ternary) => self.visit_ternary(ternary, &Position::Misc),
106            Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
107            Expression::TupleAccess(access) => self.visit_tuple_access(access, &Position::Misc),
108            Expression::Unary(unary) => self.visit_unary(unary, &Position::Misc),
109            Expression::Unit(unit) => self.visit_unit(unit, &Position::Misc),
110        }
111    }
112
113    fn visit_array_access(
114        &mut self,
115        input: &leo_ast::ArrayAccess,
116        _additional: &Self::AdditionalInput,
117    ) -> Self::Output {
118        self.visit_expression(&input.array, &Position::Misc);
119        self.visit_expression(&input.index, &Position::Misc);
120    }
121
122    fn visit_member_access(
123        &mut self,
124        input: &leo_ast::MemberAccess,
125        _additional: &Self::AdditionalInput,
126    ) -> Self::Output {
127        self.visit_expression(&input.inner, &Position::Misc);
128    }
129
130    fn visit_tuple_access(
131        &mut self,
132        input: &leo_ast::TupleAccess,
133        _additional: &Self::AdditionalInput,
134    ) -> Self::Output {
135        self.visit_expression(&input.tuple, &Position::TupleAccess);
136    }
137
138    fn visit_associated_function(
139        &mut self,
140        input: &leo_ast::AssociatedFunctionExpression,
141        _additional: &Self::AdditionalInput,
142    ) -> Self::Output {
143        let core_function = CoreFunction::from_symbols(input.variant.name, input.name.name)
144            .expect("Typechecking guarantees that this function exists.");
145        let position = if core_function == CoreFunction::FutureAwait { Position::Await } else { Position::Misc };
146        input.arguments.iter().for_each(|arg| {
147            self.visit_expression(arg, &position);
148        });
149    }
150
151    fn visit_call(&mut self, input: &leo_ast::CallExpression, _additional: &Self::AdditionalInput) -> Self::Output {
152        input.arguments.iter().for_each(|expr| {
153            self.visit_expression(expr, &Position::FunctionArgument);
154        });
155        Default::default()
156    }
157
158    fn visit_tuple(&mut self, input: &leo_ast::TupleExpression, additional: &Self::AdditionalInput) -> Self::Output {
159        let next_position = match additional {
160            Position::Definition | Position::Return => Position::LastTupleLiteral,
161            _ => Position::Misc,
162        };
163        let mut iter = input.elements.iter().peekable();
164        while let Some(expr) = iter.next() {
165            let position = if iter.peek().is_some() { &Position::Misc } else { &next_position };
166            self.visit_expression(expr, position);
167        }
168        Default::default()
169    }
170
171    /* Statments */
172    fn visit_definition(&mut self, input: &leo_ast::DefinitionStatement) {
173        if let Some(ty) = input.type_.as_ref() {
174            self.visit_type(ty)
175        }
176        self.visit_expression(&input.value, &Position::Definition);
177    }
178
179    fn visit_return(&mut self, input: &leo_ast::ReturnStatement) {
180        self.visit_expression(&input.expression, &Position::Return);
181    }
182}