neo_devpack_solidity/solidity/validate/
returns.rs1pub 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!(
126 "function '{function_name}' returns a value but is declared without a return type"
127 ))),
128 (0, None) => {}
129 (1, None) => diagnostics.push(Diagnostic::warning(format!(
133 "function '{function_name}' declares a return value but returns without one"
134 ))),
135 (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 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}