neo_solidity/solidity/validate/
returns.rs

1pub fn extract_return_expression(body: &Option<Statement>) -> Option<Expression> {
2    let statement = body.as_ref()?;
3
4    match statement {
5        Statement::Block { statements, .. } => {
6            if statements.len() == 1 {
7                if let Statement::Return(_, expr) = &statements[0] {
8                    expr.clone()
9                } else {
10                    None
11                }
12            } else {
13                None
14            }
15        }
16        Statement::Return(_, expr) => expr.clone(),
17        _ => None,
18    }
19}
20
21fn check_return_statements(
22    statement: &Statement,
23    expected_count: usize,
24    function_name: &str,
25    return_arities: &std::collections::HashMap<(String, usize), usize>,
26    diagnostics: &mut Vec<Diagnostic>,
27) {
28    match statement {
29        Statement::Block { statements, .. } => {
30            for stmt in statements {
31                check_return_statements(
32                    stmt,
33                    expected_count,
34                    function_name,
35                    return_arities,
36                    diagnostics,
37                );
38            }
39        }
40        Statement::If(_, _, then_stmt, else_stmt) => {
41            check_return_statements(
42                then_stmt,
43                expected_count,
44                function_name,
45                return_arities,
46                diagnostics,
47            );
48            if let Some(else_stmt) = else_stmt {
49                check_return_statements(
50                    else_stmt,
51                    expected_count,
52                    function_name,
53                    return_arities,
54                    diagnostics,
55                );
56            }
57        }
58        Statement::While(_, _, body) => {
59            check_return_statements(body, expected_count, function_name, return_arities, diagnostics);
60        }
61        Statement::DoWhile(_, body, _) => {
62            check_return_statements(body, expected_count, function_name, return_arities, diagnostics);
63        }
64        Statement::For(_, init, _, _, body) => {
65            if let Some(init_stmt) = init {
66                check_return_statements(
67                    init_stmt,
68                    expected_count,
69                    function_name,
70                    return_arities,
71                    diagnostics,
72                );
73            }
74            if let Some(body_stmt) = body {
75                check_return_statements(
76                    body_stmt,
77                    expected_count,
78                    function_name,
79                    return_arities,
80                    diagnostics,
81                );
82            }
83        }
84        Statement::Try(_, _, handler, catches) => {
85            if let Some((_, handler_stmt)) = handler {
86                check_return_statements(
87                    handler_stmt,
88                    expected_count,
89                    function_name,
90                    return_arities,
91                    diagnostics,
92                );
93            }
94            for catch in catches {
95                match catch {
96                    CatchClause::Simple(_, _, stmt) => {
97                        check_return_statements(
98                            stmt,
99                            expected_count,
100                            function_name,
101                            return_arities,
102                            diagnostics,
103                        )
104                    }
105                    CatchClause::Named(_, _, _, stmt) => {
106                        check_return_statements(
107                            stmt,
108                            expected_count,
109                            function_name,
110                            return_arities,
111                            diagnostics,
112                        )
113                    }
114                }
115            }
116        }
117        Statement::Return(_, expr) => match (expected_count, expr) {
118            (0, Some(_)) => diagnostics.push(Diagnostic::warning(format!(
119                "function '{}' returns a value but is declared without return type",
120                function_name
121            ))),
122            (0, None) => {}
123            (1, None) => diagnostics.push(Diagnostic::warning(format!(
124                "function '{}' declares a return value but returns without one",
125                function_name
126            ))),
127            (1, Some(_)) => {}
128            (expected, Some(expr)) => {
129                if let Expression::List(_, list) = expr {
130                    let actual = list.len();
131                    if actual != expected {
132                        diagnostics.push(Diagnostic::warning(format!(
133                            "function '{}' expected {} return values but found {}",
134                            function_name, expected, actual
135                        )));
136                    }
137                } else {
138                    let inferred = match expr {
139                        Expression::FunctionCall(_, callee, args) => {
140                            let name = match callee.as_ref() {
141                                Expression::Variable(id) => Some(id.name.as_str()),
142                                Expression::MemberAccess(_, _, member) => Some(member.name.as_str()),
143                                _ => None,
144                            };
145                            name.and_then(|name| {
146                                return_arities
147                                    .get(&(name.to_string(), args.len()))
148                                    .copied()
149                            })
150                        }
151                        _ => None,
152                    };
153
154                    if let Some(actual) = inferred {
155                        if actual != expected {
156                            diagnostics.push(Diagnostic::warning(format!(
157                                "function '{}' expected {} return values but call returns {}",
158                                function_name, expected, actual
159                            )));
160                        }
161                    } else {
162                        diagnostics.push(Diagnostic::warning(format!(
163                            "function '{}' should return {} values but expression does not match tuple",
164                            function_name, expected
165                        )));
166                    }
167                }
168            }
169            (expected, None) => diagnostics.push(Diagnostic::warning(format!(
170                "function '{}' declares {} return values but returns without one",
171                function_name, expected
172            ))),
173        },
174        _ => {}
175    }
176}