neo_solidity/ir/statements/dispatch/
try_catch.rs1fn value_type_to_catch_guard(value_type: &ValueType) -> Option<ConvertTarget> {
2 match value_type {
3 ValueType::Any => None,
4 ValueType::Boolean => Some(ConvertTarget::Boolean),
5 ValueType::Integer { .. } => Some(ConvertTarget::Integer),
6 ValueType::String | ValueType::Address | ValueType::ByteArray { .. } => {
7 Some(ConvertTarget::ByteArray)
8 }
9 ValueType::Array(_) | ValueType::Struct { .. } => Some(ConvertTarget::Array),
10 ValueType::Mapping { .. } => Some(ConvertTarget::Map),
11 }
12}
13
14fn catch_clause_param(clause: &solang_parser::pt::CatchClause) -> Option<&solang_parser::pt::Parameter> {
15 match clause {
16 solang_parser::pt::CatchClause::Simple(_, param, _) => param.as_ref(),
17 solang_parser::pt::CatchClause::Named(_, _, param, _) => Some(param),
18 }
19}
20
21fn catch_clause_statement(clause: &solang_parser::pt::CatchClause) -> &Statement {
22 match clause {
23 solang_parser::pt::CatchClause::Simple(_, _, stmt) => stmt,
24 solang_parser::pt::CatchClause::Named(_, _, _, stmt) => stmt,
25 }
26}
27
28fn is_bare_catch_clause(clause: &solang_parser::pt::CatchClause) -> bool {
29 matches!(clause, solang_parser::pt::CatchClause::Simple(_, None, _))
30}
31
32fn catch_clause_guard_target(
33 clause: &solang_parser::pt::CatchClause,
34 ctx: &mut LoweringContext,
35) -> Option<ConvertTarget> {
36 if let Some(parameter) = catch_clause_param(clause) {
37 return infer_type_from_expression(¶meter.ty, ctx)
38 .as_ref()
39 .and_then(value_type_to_catch_guard);
40 }
41
42 if let solang_parser::pt::CatchClause::Named(_, ident, _, _) = clause {
43 if ident.name == "Panic" {
44 return Some(ConvertTarget::Integer);
45 }
46 }
47
48 None
49}
50
51fn bind_catch_clause_parameter(
52 clause: &solang_parser::pt::CatchClause,
53 catch_local: usize,
54 ctx: &mut LoweringContext,
55 instructions: &mut Vec<Instruction>,
56) {
57 let Some(parameter) = catch_clause_param(clause) else {
58 return;
59 };
60
61 let Some(name) = parameter.name.as_ref().map(|id| id.name.clone()) else {
62 return;
63 };
64
65 let inferred = infer_type_from_expression(¶meter.ty, ctx).unwrap_or(ValueType::Any);
66 let slot = ctx.allocate_local(name, Some(inferred));
67 instructions.push(Instruction::LoadLocal(catch_local));
68 instructions.push(Instruction::StoreLocal(slot));
69}
70
71fn lower_try_statement(
72 expr: &Expression,
73 handler: &Option<(solang_parser::pt::ParameterList, Box<Statement>)>,
74 catches: &[solang_parser::pt::CatchClause],
75 ctx: &mut LoweringContext,
76 instructions: &mut Vec<Instruction>,
77) -> bool {
78 let catch_label = ctx.next_label();
83 let success_label = ctx.next_label();
84 let end_label = ctx.next_label();
85
86 let (call_expr, inline_success) = match expr {
87 Expression::FunctionCallBlock(_, call, block) => (call.as_ref(), Some(block.as_ref())),
88 _ => (expr, None),
89 };
90
91 let (handler_params, handler_stmt) = handler
92 .as_ref()
93 .map(|(params, stmt)| (params.as_slice(), Some(stmt.as_ref())))
94 .unwrap_or((&[][..], None));
95
96 let success_stmt = handler_stmt.or(inline_success);
97
98 if catches.is_empty() {
99 ctx.record_error_with_suggestion(
100 "try statement without catch clause is not supported",
101 "add a catch clause: try expr { ... } catch { ... }",
102 );
103 if lower_expression(call_expr, ctx, instructions) {
104 instructions.push(Instruction::Drop(ValueType::Any));
105 }
106 if let Some(success_stmt) = success_stmt {
107 let _ = lower_statement(success_stmt, ctx, instructions);
108 }
109 return false;
110 }
111
112 instructions.push(Instruction::Try {
114 catch_target: catch_label,
115 });
116
117 let mut try_return_slot: Option<(usize, ValueType)> = None;
118 if lower_expression(call_expr, ctx, instructions) {
119 if handler_params.len() == 1 {
120 if let Some(param) = handler_params[0].1.as_ref() {
121 let inferred_type =
122 infer_type_from_expression(¶m.ty, ctx).unwrap_or(ValueType::Any);
123 let tmp = ctx.allocate_local("__try_ret".to_string(), Some(inferred_type.clone()));
124 instructions.push(Instruction::StoreLocal(tmp));
125 try_return_slot = Some((tmp, inferred_type));
126 } else {
127 ctx.record_error_with_suggestion(
128 "try returns(...) parameter is missing",
129 "specify a return parameter: try func() returns (uint256 result) { ... }",
130 );
131 instructions.push(Instruction::Drop(ValueType::Any));
132 }
133 } else {
134 if !handler_params.is_empty() {
135 ctx.record_error_with_suggestion(
136 "try returns(...) with multiple values is not supported",
137 "use a single return value or a struct to aggregate multiple values",
138 );
139 }
140 instructions.push(Instruction::Drop(ValueType::Any));
141 }
142 } else if !handler_params.is_empty() {
143 ctx.record_error("try returns(...) expects a return value");
144 }
145
146 instructions.push(Instruction::EndTry {
147 target: success_label,
148 });
149
150 instructions.push(Instruction::Label(catch_label));
152 ctx.enter_scope();
153
154 if catches.len() == 1 && is_bare_catch_clause(&catches[0]) {
155 instructions.push(Instruction::Drop(ValueType::Any));
157 let _ = lower_statement(catch_clause_statement(&catches[0]), ctx, instructions);
158 } else {
159 let catch_local = ctx.allocate_local("__catch_exception".to_string(), None);
160 instructions.push(Instruction::StoreLocal(catch_local));
161
162 let mut fallback_clause: Option<&solang_parser::pt::CatchClause> = None;
163
164 for clause in catches {
165 if is_bare_catch_clause(clause) {
166 if fallback_clause.is_none() {
167 fallback_clause = Some(clause);
168 }
169 continue;
170 }
171
172 let next_clause_label = ctx.next_label();
173 if let Some(guard_target) = catch_clause_guard_target(clause, ctx) {
174 instructions.push(Instruction::LoadLocal(catch_local));
175 instructions.push(Instruction::IsType {
176 target: guard_target,
177 });
178 instructions.push(Instruction::JumpIf {
179 target: next_clause_label,
180 });
181 }
182
183 if let solang_parser::pt::CatchClause::Named(_, ident, _, _) = clause {
184 if ident.name == "Panic" {
185 eprintln!(
186 "warning: catch Panic(uint256) uses NeoVM stack-item integer checks; EVM panic code semantics do not fully apply on Neo N3"
187 );
188 }
189 }
190
191 ctx.enter_scope();
192 bind_catch_clause_parameter(clause, catch_local, ctx, instructions);
193 let _ = lower_statement(catch_clause_statement(clause), ctx, instructions);
194 ctx.exit_scope();
195 instructions.push(Instruction::Jump { target: end_label });
196 instructions.push(Instruction::Label(next_clause_label));
197 }
198
199 if let Some(clause) = fallback_clause {
200 ctx.enter_scope();
201 let _ = lower_statement(catch_clause_statement(clause), ctx, instructions);
202 ctx.exit_scope();
203 } else {
204 instructions.push(Instruction::LoadLocal(catch_local));
206 instructions.push(Instruction::Throw);
207 }
208 }
209
210 ctx.exit_scope();
211 instructions.push(Instruction::EndTry { target: end_label });
212
213 instructions.push(Instruction::Label(success_label));
215 if let Some(success_stmt) = success_stmt {
216 ctx.enter_scope();
217
218 if handler_params.len() == 1 {
219 if let Some(param) = handler_params[0].1.as_ref() {
220 if let Some(name) = param.name.as_ref().map(|id| id.name.clone()) {
221 let inferred = try_return_slot
222 .as_ref()
223 .map(|(_, ty)| ty.clone())
224 .unwrap_or(ValueType::Any);
225 let slot = ctx.allocate_local(name, Some(inferred.clone()));
226 if let Some((tmp, _)) = try_return_slot {
227 instructions.push(Instruction::LoadLocal(tmp));
228 instructions.push(Instruction::StoreLocal(slot));
229 } else {
230 push_default_for_value_type(&inferred, ctx, instructions);
231 instructions.push(Instruction::StoreLocal(slot));
232 }
233 }
234 }
235 }
236
237 let _ = lower_statement(success_stmt, ctx, instructions);
238 ctx.exit_scope();
239 }
240
241 instructions.push(Instruction::Label(end_label));
242 false
243}