1use proc_macro2::TokenStream;
2use pyo3::{Borrowed, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods};
3use quote::quote;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 CodeGen, CodeGenContext, ExprType, Node, PythonOptions, Statement, SymbolTableScopes,
8 extract_list,
9};
10
11#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
13pub struct Try {
14 pub body: Vec<Statement>,
16 pub handlers: Vec<ExceptHandler>,
18 pub orelse: Vec<Statement>,
20 pub finalbody: Vec<Statement>,
22 pub lineno: Option<usize>,
24 pub col_offset: Option<usize>,
25 pub end_lineno: Option<usize>,
26 pub end_col_offset: Option<usize>,
27}
28
29#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
31pub struct ExceptHandler {
32 pub exception_type: Option<ExprType>,
34 pub name: Option<String>,
36 pub body: Vec<Statement>,
38 pub lineno: Option<usize>,
40 pub col_offset: Option<usize>,
41 pub end_lineno: Option<usize>,
42 pub end_col_offset: Option<usize>,
43}
44
45impl<'a, 'py> FromPyObject<'a, 'py> for Try {
46 type Error = pyo3::PyErr;
47 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
48 let body: Vec<Statement> = extract_list(&ob, "body", "try body")?;
50
51 let handlers: Vec<ExceptHandler> = extract_list(&ob, "handlers", "try handlers")?;
53
54 let orelse: Vec<Statement> = extract_list(&ob, "orelse", "try orelse").unwrap_or_default();
56
57 let finalbody: Vec<Statement> = extract_list(&ob, "finalbody", "try finalbody").unwrap_or_default();
59
60 Ok(Try {
61 body,
62 handlers,
63 orelse,
64 finalbody,
65 lineno: ob.lineno(),
66 col_offset: ob.col_offset(),
67 end_lineno: ob.end_lineno(),
68 end_col_offset: ob.end_col_offset(),
69 })
70 }
71}
72
73impl<'a, 'py> FromPyObject<'a, 'py> for ExceptHandler {
74 type Error = pyo3::PyErr;
75 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
76 let exception_type: Option<ExprType> = if let Ok(type_attr) = ob.getattr("type") {
78 if type_attr.is_none() {
79 None
80 } else {
81 Some(type_attr.extract()?)
82 }
83 } else {
84 None
85 };
86
87 let name: Option<String> = if let Ok(name_attr) = ob.getattr("name") {
89 if name_attr.is_none() {
90 None
91 } else {
92 Some(name_attr.extract()?)
93 }
94 } else {
95 None
96 };
97
98 let body: Vec<Statement> = extract_list(&ob, "body", "except handler body")?;
100
101 Ok(ExceptHandler {
102 exception_type,
103 name,
104 body,
105 lineno: ob.lineno(),
106 col_offset: ob.col_offset(),
107 end_lineno: ob.end_lineno(),
108 end_col_offset: ob.end_col_offset(),
109 })
110 }
111}
112
113impl Node for Try {
114 fn lineno(&self) -> Option<usize> { self.lineno }
115 fn col_offset(&self) -> Option<usize> { self.col_offset }
116 fn end_lineno(&self) -> Option<usize> { self.end_lineno }
117 fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
118}
119
120impl Node for ExceptHandler {
121 fn lineno(&self) -> Option<usize> { self.lineno }
122 fn col_offset(&self) -> Option<usize> { self.col_offset }
123 fn end_lineno(&self) -> Option<usize> { self.end_lineno }
124 fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
125}
126
127impl CodeGen for Try {
128 type Context = CodeGenContext;
129 type Options = PythonOptions;
130 type SymbolTable = SymbolTableScopes;
131
132 fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
133 let symbols = self.body.into_iter().fold(symbols, |acc, stmt| stmt.find_symbols(acc));
135 let symbols = self.handlers.into_iter().fold(symbols, |acc, handler| {
136 let symbols = handler.body.into_iter().fold(acc, |acc, stmt| stmt.find_symbols(acc));
137 if let Some(exception_type) = handler.exception_type {
138 exception_type.find_symbols(symbols)
139 } else {
140 symbols
141 }
142 });
143 let symbols = self.orelse.into_iter().fold(symbols, |acc, stmt| stmt.find_symbols(acc));
144 self.finalbody.into_iter().fold(symbols, |acc, stmt| stmt.find_symbols(acc))
145 }
146
147 fn to_rust(
148 self,
149 ctx: Self::Context,
150 options: Self::Options,
151 symbols: Self::SymbolTable,
152 ) -> Result<TokenStream, Box<dyn std::error::Error>> {
153 let has_return = crate::body_contains_function_return(&self.body);
159 let body_escapes = crate::body_breaks_outward(&self.body);
163 if !self.finalbody.is_empty() {
168 let where_ = if self.handlers.iter().any(|h| crate::body_breaks_outward(&h.body)) {
169 Some("except handler")
170 } else if crate::body_breaks_outward(&self.orelse) {
171 Some("else clause")
172 } else {
173 None
174 };
175 if let Some(where_) = where_ {
176 return Err(format!(
177 "`break`/`continue` in a try statement's {} is not supported when the \
178 statement also has a `finally` clause; move the loop control out of the \
179 handler, or drop the finally clause",
180 where_
181 )
182 .into());
183 }
184 }
185 let body_for_guarantee = self.body.clone();
186 let body_ctx = CodeGenContext::TryBlock {
187 parent: Box::new(ctx.clone()),
188 };
189 let try_body_tokens: Result<Vec<TokenStream>, Box<dyn std::error::Error>> = self
190 .body
191 .into_iter()
192 .map(|stmt| stmt.to_rust(body_ctx.clone(), options.clone(), symbols.clone()))
193 .collect();
194 let try_body_tokens = try_body_tokens?;
195
196 let break_return = if ctx.in_try_block() {
201 quote!(return Ok(PyFlow::Return(__rython_ret));)
202 } else {
203 quote!(return Ok(__rython_ret);)
204 };
205
206 let has_finally = !self.finalbody.is_empty();
207 let finally_tokens = if has_finally {
208 let finally_body_tokens: Result<Vec<TokenStream>, Box<dyn std::error::Error>> = self
209 .finalbody
210 .clone()
211 .into_iter()
212 .map(|stmt| stmt.to_rust(ctx.clone(), options.clone(), symbols.clone()))
213 .collect();
214 let finally_body_tokens = finally_body_tokens?;
215 quote! { #(#finally_body_tokens;)* }
216 } else {
217 quote!()
218 };
219
220 let handler_ctx = CodeGenContext::ExceptHandler {
226 parent: Box::new(ctx.clone()),
227 };
228 let mut arms: Vec<TokenStream> = Vec::new();
229 let mut has_catch_all = false;
230 for handler in self.handlers {
231 let guard = match &handler.exception_type {
232 None => None,
233 Some(t) => exception_match_guard(t)?,
234 };
235 let bind = match &handler.name {
236 Some(name) => {
237 let ident = crate::safe_ident(name);
238 quote! {
239 #[allow(unused_variables, unused_mut)]
240 let mut #ident = __rython_exc.clone();
241 }
242 }
243 None => quote!(),
244 };
245 let arm_body = lower_finally_guarded_body(
246 handler.body,
247 handler_ctx.clone(),
248 &options,
249 &symbols,
250 has_finally,
251 &finally_tokens,
252 &break_return,
253 "handler body terminates on every path",
254 )?;
255 match guard {
256 Some(g) => arms.push(quote! {
257 Err(__rython_exc) if #g => { #bind #arm_body }
258 }),
259 None => {
260 has_catch_all = true;
261 arms.push(quote! {
262 Err(__rython_exc) => { #bind #arm_body }
263 });
264 break; }
266 }
267 }
268
269 let else_tokens = if !self.orelse.is_empty() {
273 lower_finally_guarded_body(
274 self.orelse,
275 ctx.clone(),
276 &options,
277 &symbols,
278 has_finally,
279 &finally_tokens,
280 &break_return,
281 "else clause terminates on every path",
282 )?
283 } else {
284 quote!()
285 };
286
287 let ok_arm_body = if crate::guarantees_return(&body_for_guarantee) {
292 quote!(unreachable!("try body terminates on every path"))
293 } else {
294 else_tokens
295 };
296
297 if !has_catch_all {
301 arms.push(quote! {
302 Err(__rython_exc) => { #finally_tokens return Err(__rython_exc); }
303 });
304 }
305
306 if has_return || body_escapes {
307 let flow_type = if has_return {
311 quote!(PyFlow<_>)
312 } else {
313 quote!(PyFlow<()>)
314 };
315 let return_arm = if has_return {
316 quote! {
317 Ok(PyFlow::Return(__rython_ret)) => {
318 #finally_tokens
319 #break_return
320 }
321 }
322 } else {
323 quote! { Ok(PyFlow::Return(_)) => unreachable!("try body has no return"), }
324 };
325 let (break_arm, continue_arm) = if body_escapes {
330 let replay_break = if ctx.break_crosses_try_closure() {
331 quote!(return Ok(PyFlow::Break);)
332 } else if ctx.break_target_has_else() {
333 quote!({ __rython_broke = true; break; })
334 } else {
335 quote!(break;)
336 };
337 let replay_continue = if ctx.break_crosses_try_closure() {
338 quote!(return Ok(PyFlow::Continue);)
339 } else {
340 quote!(continue;)
341 };
342 (
343 quote! { Ok(PyFlow::Break) => { #finally_tokens #replay_break } },
344 quote! { Ok(PyFlow::Continue) => { #finally_tokens #replay_continue } },
345 )
346 } else {
347 (
348 quote! { Ok(PyFlow::Break) => unreachable!("try body has no break"), },
349 quote! { Ok(PyFlow::Continue) => unreachable!("try body has no continue"), },
350 )
351 };
352 Ok(quote! {
353 {
354 #[allow(unreachable_code)]
355 let __rython_try_result: std::result::Result<
356 #flow_type,
357 PyException,
358 > = (|| {
359 #(#try_body_tokens;)*
360 Ok(PyFlow::Normal)
361 })();
362 match __rython_try_result {
363 #return_arm
364 #break_arm
365 #continue_arm
366 Ok(PyFlow::Normal) => { #ok_arm_body }
367 #(#arms)*
368 }
369 #finally_tokens
370 }
371 })
372 } else {
373 Ok(quote! {
374 {
375 #[allow(unreachable_code)]
376 let __rython_try_result: std::result::Result<(), PyException> = (|| {
377 #(#try_body_tokens;)*
378 Ok(())
379 })();
380 match __rython_try_result {
381 Ok(()) => { #ok_arm_body }
382 #(#arms)*
383 }
384 #finally_tokens
385 }
386 })
387 }
388 }
389}
390
391#[allow(clippy::too_many_arguments)]
397fn lower_finally_guarded_body(
398 body: Vec<Statement>,
399 base_ctx: CodeGenContext,
400 options: &PythonOptions,
401 symbols: &SymbolTableScopes,
402 has_finally: bool,
403 finally_tokens: &TokenStream,
404 break_return: &TokenStream,
405 unreachable_note: &str,
406) -> Result<TokenStream, Box<dyn std::error::Error>> {
407 if !has_finally {
408 let tokens: Result<Vec<TokenStream>, Box<dyn std::error::Error>> = body
409 .into_iter()
410 .map(|stmt| stmt.to_rust(base_ctx.clone(), options.clone(), symbols.clone()))
411 .collect();
412 let tokens = tokens?;
413 return Ok(quote! { #(#tokens;)* });
414 }
415
416 let guarantees = crate::guarantees_return(&body);
417 let has_ret = crate::body_contains_function_return(&body);
418 let inner_ctx = CodeGenContext::TryBlock {
419 parent: Box::new(base_ctx),
420 };
421 let tokens: Result<Vec<TokenStream>, Box<dyn std::error::Error>> = body
422 .into_iter()
423 .map(|stmt| stmt.to_rust(inner_ctx.clone(), options.clone(), symbols.clone()))
424 .collect();
425 let tokens = tokens?;
426
427 let completed_arm = if guarantees {
428 quote!(unreachable!(#unreachable_note))
429 } else {
430 quote!()
431 };
432
433 if has_ret {
434 Ok(quote! {
435 #[allow(unreachable_code)]
436 let __rython_inner: std::result::Result<
437 PyFlow<_>,
438 PyException,
439 > = (|| {
440 #(#tokens;)*
441 Ok(PyFlow::Normal)
442 })();
443 match __rython_inner {
444 Ok(PyFlow::Return(__rython_ret)) => {
445 #finally_tokens
446 #break_return
447 }
448 Ok(PyFlow::Break) => unreachable!("handler body has no break"),
452 Ok(PyFlow::Continue) => unreachable!("handler body has no continue"),
453 Ok(PyFlow::Normal) => { #completed_arm }
454 Err(__rython_reraise) => {
455 #finally_tokens
456 return Err(__rython_reraise);
457 }
458 }
459 })
460 } else {
461 Ok(quote! {
462 #[allow(unreachable_code)]
463 let __rython_inner: std::result::Result<(), PyException> = (|| {
464 #(#tokens;)*
465 Ok(())
466 })();
467 match __rython_inner {
468 Ok(()) => { #completed_arm }
469 Err(__rython_reraise) => {
470 #finally_tokens
471 return Err(__rython_reraise);
472 }
473 }
474 })
475 }
476}
477
478fn exception_match_guard(
483 exception_type: &ExprType,
484) -> Result<Option<TokenStream>, Box<dyn std::error::Error>> {
485 match exception_type {
486 ExprType::Name(name) => {
487 let n = &name.id;
488 Ok(Some(quote!(__rython_exc.matches(#n))))
489 }
490 ExprType::Attribute(attr) => {
491 let n = &attr.attr;
492 Ok(Some(quote!(__rython_exc.matches(#n))))
493 }
494 ExprType::Tuple(tuple) => {
495 let mut guards = Vec::new();
496 for elt in &tuple.elts {
497 match exception_match_guard(elt)? {
498 Some(g) => guards.push(g),
499 None => return Ok(None),
500 }
501 }
502 if guards.is_empty() {
503 Ok(None)
504 } else {
505 Ok(Some(quote!(#(#guards)||*)))
506 }
507 }
508 other => Err(format!(
509 "unsupported exception type in except clause: {:?} (use a name, \
510 dotted name, or tuple of names)",
511 other
512 )
513 .into()),
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 }