Skip to main content

neo_devpack_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            // Returning a value from a no-return function is invalid Solidity,
119            // but the synthesized getter for a `public` state variable reaches
120            // here with `expected_count == 0` (its return type is not tracked
121            // in the metadata the validator sees), so promoting this to an
122            // error false-positives on valid auto-getters. Keep it a warning;
123            // only the explicit-tuple arity mismatches below (which a getter
124            // never produces) are promoted to hard errors.
125            (0, Some(_)) => diagnostics.push(Diagnostic::warning(format!(
126                "function '{function_name}' returns a value but is declared without a return type"
127            ))),
128            (0, None) => {}
129            // `return;` with a declared return is VALID when the returns are
130            // NAMED (their current values are returned), so this stays a
131            // warning to avoid false-positives on valid named-return functions.
132            (1, None) => diagnostics.push(Diagnostic::warning(format!(
133                "function '{function_name}' declares a return value but returns without one"
134            ))),
135            // A single declared return given an explicit multi-element tuple is
136            // a definite arity mismatch — hard error.
137            (1, Some(Expression::List(_, list))) if list.len() != 1 => {
138                diagnostics.push(Diagnostic::error(format!(
139                    "function '{function_name}' declares 1 return value but returns a {}-tuple",
140                    list.len()
141                )))
142            }
143            (1, Some(_)) => {}
144            (expected, Some(expr)) => {
145                if let Expression::List(_, list) = expr {
146                    let actual = list.len();
147                    if actual != expected {
148                        // An explicit `return (a, b, ...)` whose element count
149                        // disagrees with the declaration is a definite mismatch.
150                        diagnostics.push(Diagnostic::error(format!(
151                            "function '{function_name}' expected {expected} return values but found {actual}"
152                        )));
153                    }
154                } else {
155                    let inferred = match expr {
156                        Expression::FunctionCall(_, callee, args) => {
157                            let name = match callee.as_ref() {
158                                Expression::Variable(id) => Some(id.name.as_str()),
159                                Expression::MemberAccess(_, _, member) => Some(member.name.as_str()),
160                                _ => None,
161                            };
162                            name.and_then(|name| {
163                                return_arities
164                                    .get(&(name.to_string(), args.len()))
165                                    .copied()
166                            })
167                        }
168                        _ => None,
169                    };
170
171                    if let Some(actual) = inferred {
172                        if actual != expected {
173                            diagnostics.push(Diagnostic::warning(format!(
174                                "function '{function_name}' expected {expected} return values but call returns {actual}"
175                            )));
176                        }
177                    } else {
178                        diagnostics.push(Diagnostic::warning(format!(
179                            "function '{function_name}' should return {expected} values but expression does not match tuple"
180                        )));
181                    }
182                }
183            }
184            (expected, None) => diagnostics.push(Diagnostic::warning(format!(
185                "function '{function_name}' declares {expected} return values but returns without one"
186            ))),
187        },
188        _ => {}
189    }
190}