Skip to main content

shape_ext_python/
error_mapping.rs

1//! Python traceback -> Shape source location mapping.
2
3use crate::runtime::CompiledFunction;
4
5/// Parsed representation of a Python traceback frame.
6#[derive(Debug, Clone)]
7pub struct PythonFrame {
8    pub filename: String,
9    pub line: u32,
10    pub function: String,
11    pub text: Option<String>,
12}
13
14/// Parse a Python traceback string into structured frames.
15pub fn parse_traceback(_traceback: &str) -> Vec<PythonFrame> {
16    Vec::new()
17}
18
19/// Map a Python line number inside `__shape_fn__` back to the Shape
20/// source line number.
21pub fn map_python_line_to_shape(python_line: u32, shape_body_start_line: u32) -> u32 {
22    if python_line < 2 {
23        shape_body_start_line
24    } else {
25        shape_body_start_line + (python_line - 1)
26    }
27}
28
29/// Format a Python error with context from the compiled function.
30#[cfg(feature = "pyo3")]
31pub fn format_python_error(
32    py: pyo3::Python<'_>,
33    err: &pyo3::PyErr,
34    func: &CompiledFunction,
35) -> String {
36    use pyo3::types::PyTracebackMethods;
37    let traceback_str = err
38        .traceback(py)
39        .and_then(|tb| tb.format().ok())
40        .unwrap_or_default();
41
42    // Try to extract the relevant line number from the traceback
43    let mut shape_line = None;
44    for line in traceback_str.lines() {
45        if line.contains("<shape>") || line.contains("__shape__") {
46            // Parse "line N" from the traceback
47            if let Some(pos) = line.find("line ") {
48                let after = &line[pos + 5..];
49                if let Some(end) = after.find(|c: char| !c.is_ascii_digit()) {
50                    if let Ok(py_line) = after[..end].parse::<u32>() {
51                        shape_line = Some(map_python_line_to_shape(
52                            py_line,
53                            func.shape_body_start_line,
54                        ));
55                    }
56                } else if let Ok(py_line) = after.trim().parse::<u32>() {
57                    shape_line = Some(map_python_line_to_shape(
58                        py_line,
59                        func.shape_body_start_line,
60                    ));
61                }
62            }
63        }
64    }
65
66    if let Some(line) = shape_line {
67        format!("Python error in '{}' at line {}: {}", func.name, line, err)
68    } else {
69        format!("Python error in '{}': {}", func.name, err)
70    }
71}
72
73/// Fallback when pyo3 is not enabled.
74#[cfg(not(feature = "pyo3"))]
75pub fn format_python_error(_err: &str, func: &CompiledFunction) -> String {
76    format!("Python error in '{}': pyo3 not enabled", func.name)
77}